diff options
author | Paul Gilbert | 2013-11-30 20:44:23 -0500 |
---|---|---|
committer | Paul Gilbert | 2013-11-30 20:44:23 -0500 |
commit | ede418b67a0f14e4f17a2b03f5362741badd5532 (patch) | |
tree | 07de039fac5c303f1b9fce372afe5fa19854f547 /common | |
parent | 66d1f7a8de2ff5a21ad013f45924c406f4833e9a (diff) | |
parent | 3e859768770a0b385e21c4528cd546b33ed9a55d (diff) | |
download | scummvm-rg350-ede418b67a0f14e4f17a2b03f5362741badd5532.tar.gz scummvm-rg350-ede418b67a0f14e4f17a2b03f5362741badd5532.tar.bz2 scummvm-rg350-ede418b67a0f14e4f17a2b03f5362741badd5532.zip |
VOYEUR: Merge of upstream
Diffstat (limited to 'common')
39 files changed, 1883 insertions, 757 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/archive.cpp b/common/archive.cpp index 1323f14805..57ebeb2ca6 100644 --- a/common/archive.cpp +++ b/common/archive.cpp @@ -118,7 +118,7 @@ void SearchSet::addDirectory(const String &name, const FSNode &dir, int priority add(name, new FSDirectory(dir, depth, flat), priority); } -void SearchSet::addSubDirectoriesMatching(const FSNode &directory, String origPattern, bool ignoreCase, int priority) { +void SearchSet::addSubDirectoriesMatching(const FSNode &directory, String origPattern, bool ignoreCase, int priority, int depth, bool flat) { FSList subDirs; if (!directory.getChildren(subDirs)) return; @@ -161,9 +161,9 @@ void SearchSet::addSubDirectoriesMatching(const FSNode &directory, String origPa } if (nextPattern.empty()) - addDirectory(name, *i, priority); + addDirectory(name, *i, priority, depth, flat); else - addSubDirectoriesMatching(*i, nextPattern, ignoreCase, priority); + addSubDirectoriesMatching(*i, nextPattern, ignoreCase, priority, depth, flat); } } } diff --git a/common/archive.h b/common/archive.h index ffd86d786d..2f9736f032 100644 --- a/common/archive.h +++ b/common/archive.h @@ -184,8 +184,8 @@ public: * to assume that this method is using anything other than a simple case insensitive compare. * Thus do not use any tokens like '*' or '?' in the "caselessName" parameter of this function! */ - void addSubDirectoryMatching(const FSNode &directory, const String &caselessName, int priority = 0) { - addSubDirectoriesMatching(directory, caselessName, true, priority); + void addSubDirectoryMatching(const FSNode &directory, const String &caselessName, int priority = 0, int depth = 1, bool flat = false) { + addSubDirectoriesMatching(directory, caselessName, true, priority, depth, flat); } /** @@ -208,7 +208,7 @@ public: * * @see Common::matchString */ - void addSubDirectoriesMatching(const FSNode &directory, String origPattern, bool ignoreCase, int priority = 0); + void addSubDirectoriesMatching(const FSNode &directory, String origPattern, bool ignoreCase, int priority = 0, int depth = 1, bool flat = false); /** * Remove an archive from the searchable set. diff --git a/common/c++11-compat.h b/common/c++11-compat.h index 50d79bd79e..f963ba9ac8 100644 --- a/common/c++11-compat.h +++ b/common/c++11-compat.h @@ -31,7 +31,9 @@ // Custom nullptr replacement. This is not type safe as the real C++11 nullptr // though. // +#if !defined(nullptr) // XCode 5.0.1 has __cplusplus=199711 but defines this #define nullptr 0 +#endif // // Replacement for the override keyword. This allows compilation of code diff --git a/common/config-manager.h b/common/config-manager.h index d43a7bec51..6bf56749c5 100644 --- a/common/config-manager.h +++ b/common/config-manager.h @@ -24,7 +24,6 @@ #define COMMON_CONFIG_MANAGER_H #include "common/array.h" -//#include "common/config-file.h" #include "common/hashmap.h" #include "common/singleton.h" #include "common/str.h" @@ -47,12 +46,33 @@ class ConfigManager : public Singleton<ConfigManager> { public: - class Domain : public StringMap { + class Domain { private: + StringMap _entries; StringMap _keyValueComments; String _domainComment; public: + typedef StringMap::const_iterator const_iterator; + const_iterator begin() const { return _entries.begin(); } + const_iterator end() const { return _entries.end(); } + + bool empty() const { return _entries.empty(); } + + bool contains(const String &key) const { return _entries.contains(key); } + + String &operator[](const String &key) { return _entries[key]; } + const String &operator[](const String &key) const { return _entries[key]; } + + void setVal(const String &key, const String &value) { _entries.setVal(key, value); } + + String &getVal(const String &key) { return _entries.getVal(key); } + const String &getVal(const String &key) const { return _entries.getVal(key); } + + void clear() { _entries.clear(); } + + void erase(const String &key) { _entries.erase(key); } + void setDomainComment(const String &comment); const String &getDomainComment() const; @@ -143,7 +163,8 @@ public: bool hasMiscDomain(const String &domName) const; const DomainMap & getGameDomains() const { return _gameDomains; } - DomainMap & getGameDomains() { return _gameDomains; } + DomainMap::iterator beginGameDomains() { return _gameDomains.begin(); } + DomainMap::iterator endGameDomains() { return _gameDomains.end(); } static void defragment(); // move in memory to reduce fragmentation void copyFrom(ConfigManager &source); diff --git a/common/coroutines.h b/common/coroutines.h index 834c67f6e4..30b9bb6a99 100644 --- a/common/coroutines.h +++ b/common/coroutines.h @@ -128,7 +128,7 @@ public: */ #define CORO_BEGIN_CONTEXT \ struct CoroContextTag : Common::CoroBaseContext { \ - CoroContextTag() : CoroBaseContext(SCUMMVM_CURRENT_FUNCTION) {} \ + CoroContextTag() : CoroBaseContext(SCUMMVM_CURRENT_FUNCTION) { DUMMY = 0; } \ int DUMMY /** @@ -146,6 +146,7 @@ public: #define CORO_BEGIN_CODE(x) \ if (&coroParam == &Common::nullContext) assert(!Common::nullContext); \ if (!x) { coroParam = x = new CoroContextTag(); } \ + x->DUMMY = 0; \ Common::CoroContextHolder tmpHolder(coroParam); \ switch (coroParam->_line) { case 0:; diff --git a/common/cosinetables.cpp b/common/cosinetables.cpp index fe8f454e14..3b245750fa 100644 --- a/common/cosinetables.cpp +++ b/common/cosinetables.cpp @@ -34,10 +34,10 @@ CosineTable::CosineTable(int bitPrecision) { int m = 1 << _bitPrecision; double freq = 2 * M_PI / m; - _table = new float[m]; + _table = new float[m / 2]; - // Table contains cos(2*pi*x/n) for 0<=x<=n/4, - // followed by its reverse + // Table contains cos(2*pi*i/m) for 0<=i<m/4, + // followed by 3m/4<=i<m for (int i = 0; i <= m / 4; i++) _table[i] = cos(i * freq); diff --git a/common/cosinetables.h b/common/cosinetables.h index f9fb6fd59a..f5c95ec003 100644 --- a/common/cosinetables.h +++ b/common/cosinetables.h @@ -36,7 +36,14 @@ public: ~CosineTable(); /** - * Get pointer to table + * Get pointer to table. + * + * This table contains 2^bitPrecision/2 entries. + * The layout of this table is as follows: + * - Entries 0 up to (excluding) 2^bitPrecision/4: + * cos(0) till (excluding) cos(1/2*pi) + * - Entries 2^bitPrecision/4 up to (excluding) 2^bitPrecision/2: + * cos(3/2*pi) till (excluding) cos(2*pi) */ const float *getTable() { return _table; } diff --git a/common/dcl.cpp b/common/dcl.cpp index 1879be992d..87ec0ad915 100644 --- a/common/dcl.cpp +++ b/common/dcl.cpp @@ -371,12 +371,14 @@ bool DecompressorDCL::unpack(ReadStream *src, byte *dest, uint32 nPacked, uint32 debug(8, "\nCOPY(%d from %d)\n", val_length, val_distance); if (val_length + _dwWrote > _szUnpacked) { - warning("DCL-INFLATE Error: Write out of bounds while copying %d bytes", val_length); + warning("DCL-INFLATE Error: Write out of bounds while copying %d bytes (declared unpacked size is %d bytes, current is %d + %d bytes)", + val_length, _szUnpacked, _dwWrote, val_length); return false; } if (_dwWrote < val_distance) { - warning("DCL-INFLATE Error: Attempt to copy from before beginning of input stream"); + warning("DCL-INFLATE Error: Attempt to copy from before beginning of input stream (declared unpacked size is %d bytes, current is %d bytes)", + _szUnpacked, _dwWrote); return false; } 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/config-file.cpp b/common/ini-file.cpp index 0ce6dcf0c8..be5247dcfb 100644 --- a/common/config-file.cpp +++ b/common/ini-file.cpp @@ -20,7 +20,7 @@ * */ -#include "common/config-file.h" +#include "common/ini-file.h" #include "common/file.h" #include "common/savefile.h" #include "common/system.h" @@ -28,24 +28,24 @@ namespace Common { -bool ConfigFile::isValidName(const String &name) { +bool INIFile::isValidName(const String &name) { const char *p = name.c_str(); while (*p && (isAlnum(*p) || *p == '-' || *p == '_' || *p == '.')) p++; return *p == 0; } -ConfigFile::ConfigFile() { +INIFile::INIFile() { } -ConfigFile::~ConfigFile() { +INIFile::~INIFile() { } -void ConfigFile::clear() { +void INIFile::clear() { _sections.clear(); } -bool ConfigFile::loadFromFile(const String &filename) { +bool INIFile::loadFromFile(const String &filename) { File file; if (file.open(filename)) return loadFromStream(file); @@ -53,7 +53,7 @@ bool ConfigFile::loadFromFile(const String &filename) { return false; } -bool ConfigFile::loadFromSaveFile(const char *filename) { +bool INIFile::loadFromSaveFile(const char *filename) { assert(g_system); SaveFileManager *saveFileMan = g_system->getSavefileManager(); SeekableReadStream *loadFile; @@ -67,7 +67,7 @@ bool ConfigFile::loadFromSaveFile(const char *filename) { return status; } -bool ConfigFile::loadFromStream(SeekableReadStream &stream) { +bool INIFile::loadFromStream(SeekableReadStream &stream) { Section section; KeyValue kv; String comment; @@ -112,9 +112,9 @@ bool ConfigFile::loadFromStream(SeekableReadStream &stream) { p++; if (*p == '\0') - error("ConfigFile::loadFromStream: missing ] in line %d", lineno); + error("INIFile::loadFromStream: missing ] in line %d", lineno); else if (*p != ']') - error("ConfigFile::loadFromStream: Invalid character '%c' occurred in section name in line %d", *p, lineno); + error("INIFile::loadFromStream: Invalid character '%c' occurred in section name in line %d", *p, lineno); // Previous section is finished now, store it. if (!section.name.empty()) @@ -140,7 +140,7 @@ bool ConfigFile::loadFromStream(SeekableReadStream &stream) { // If no section has been set, this config file is invalid! if (section.name.empty()) { - error("ConfigFile::loadFromStream: Key/value pair found outside a section in line %d", lineno); + error("INIFile::loadFromStream: Key/value pair found outside a section in line %d", lineno); } // Split string at '=' into 'key' and 'value'. First, find the "=" delimeter. @@ -173,7 +173,7 @@ bool ConfigFile::loadFromStream(SeekableReadStream &stream) { return (!stream.err() || stream.eos()); } -bool ConfigFile::saveToFile(const String &filename) { +bool INIFile::saveToFile(const String &filename) { DumpFile file; if (file.open(filename)) return saveToStream(file); @@ -181,7 +181,7 @@ bool ConfigFile::saveToFile(const String &filename) { return false; } -bool ConfigFile::saveToSaveFile(const char *filename) { +bool INIFile::saveToSaveFile(const char *filename) { assert(g_system); SaveFileManager *saveFileMan = g_system->getSavefileManager(); WriteStream *saveFile; @@ -195,7 +195,7 @@ bool ConfigFile::saveToSaveFile(const char *filename) { return status; } -bool ConfigFile::saveToStream(WriteStream &stream) { +bool INIFile::saveToStream(WriteStream &stream) { for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) { // Write out the section comment, if any if (! i->comment.empty()) { @@ -226,7 +226,7 @@ bool ConfigFile::saveToStream(WriteStream &stream) { return !stream.err(); } -void ConfigFile::addSection(const String §ion) { +void INIFile::addSection(const String §ion) { Section *s = getSection(section); if (s) return; @@ -236,7 +236,7 @@ void ConfigFile::addSection(const String §ion) { _sections.push_back(newSection); } -void ConfigFile::removeSection(const String §ion) { +void INIFile::removeSection(const String §ion) { assert(isValidName(section)); for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) { if (section.equalsIgnoreCase(i->name)) { @@ -246,13 +246,13 @@ void ConfigFile::removeSection(const String §ion) { } } -bool ConfigFile::hasSection(const String §ion) const { +bool INIFile::hasSection(const String §ion) const { assert(isValidName(section)); const Section *s = getSection(section); return s != 0; } -void ConfigFile::renameSection(const String &oldName, const String &newName) { +void INIFile::renameSection(const String &oldName, const String &newName) { assert(isValidName(oldName)); assert(isValidName(newName)); @@ -262,7 +262,7 @@ void ConfigFile::renameSection(const String &oldName, const String &newName) { // HACK: For now we just print a warning, for more info see the TODO // below. if (ns) - warning("ConfigFile::renameSection: Section name \"%s\" already used", newName.c_str()); + warning("INIFile::renameSection: Section name \"%s\" already used", newName.c_str()); else os->name = newName; } @@ -274,7 +274,7 @@ void ConfigFile::renameSection(const String &oldName, const String &newName) { } -bool ConfigFile::hasKey(const String &key, const String §ion) const { +bool INIFile::hasKey(const String &key, const String §ion) const { assert(isValidName(key)); assert(isValidName(section)); @@ -284,7 +284,7 @@ bool ConfigFile::hasKey(const String &key, const String §ion) const { return s->hasKey(key); } -void ConfigFile::removeKey(const String &key, const String §ion) { +void INIFile::removeKey(const String &key, const String §ion) { assert(isValidName(key)); assert(isValidName(section)); @@ -293,7 +293,7 @@ void ConfigFile::removeKey(const String &key, const String §ion) { s->removeKey(key); } -bool ConfigFile::getKey(const String &key, const String §ion, String &value) const { +bool INIFile::getKey(const String &key, const String §ion, String &value) const { assert(isValidName(key)); assert(isValidName(section)); @@ -307,7 +307,7 @@ bool ConfigFile::getKey(const String &key, const String §ion, String &value) return true; } -void ConfigFile::setKey(const String &key, const String §ion, const String &value) { +void INIFile::setKey(const String &key, const String §ion, const String &value) { assert(isValidName(key)); assert(isValidName(section)); // TODO: Verify that value is valid, too. In particular, it shouldn't @@ -329,13 +329,13 @@ void ConfigFile::setKey(const String &key, const String §ion, const String & } } -const ConfigFile::SectionKeyList ConfigFile::getKeys(const String §ion) const { +const INIFile::SectionKeyList INIFile::getKeys(const String §ion) const { const Section *s = getSection(section); return s->getKeys(); } -ConfigFile::Section *ConfigFile::getSection(const String §ion) { +INIFile::Section *INIFile::getSection(const String §ion) { for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) { if (section.equalsIgnoreCase(i->name)) { return &(*i); @@ -344,7 +344,7 @@ ConfigFile::Section *ConfigFile::getSection(const String §ion) { return 0; } -const ConfigFile::Section *ConfigFile::getSection(const String §ion) const { +const INIFile::Section *INIFile::getSection(const String §ion) const { for (List<Section>::const_iterator i = _sections.begin(); i != _sections.end(); ++i) { if (section.equalsIgnoreCase(i->name)) { return &(*i); @@ -353,11 +353,11 @@ const ConfigFile::Section *ConfigFile::getSection(const String §ion) const { return 0; } -bool ConfigFile::Section::hasKey(const String &key) const { +bool INIFile::Section::hasKey(const String &key) const { return getKey(key) != 0; } -const ConfigFile::KeyValue* ConfigFile::Section::getKey(const String &key) const { +const INIFile::KeyValue* INIFile::Section::getKey(const String &key) const { for (List<KeyValue>::const_iterator i = keys.begin(); i != keys.end(); ++i) { if (key.equalsIgnoreCase(i->key)) { return &(*i); @@ -366,7 +366,7 @@ const ConfigFile::KeyValue* ConfigFile::Section::getKey(const String &key) const return 0; } -void ConfigFile::Section::setKey(const String &key, const String &value) { +void INIFile::Section::setKey(const String &key, const String &value) { for (List<KeyValue>::iterator i = keys.begin(); i != keys.end(); ++i) { if (key.equalsIgnoreCase(i->key)) { i->value = value; @@ -380,7 +380,7 @@ void ConfigFile::Section::setKey(const String &key, const String &value) { keys.push_back(newKV); } -void ConfigFile::Section::removeKey(const String &key) { +void INIFile::Section::removeKey(const String &key) { for (List<KeyValue>::iterator i = keys.begin(); i != keys.end(); ++i) { if (key.equalsIgnoreCase(i->key)) { keys.erase(i); diff --git a/common/config-file.h b/common/ini-file.h index 8bba851110..1d94ce7bdc 100644 --- a/common/config-file.h +++ b/common/ini-file.h @@ -20,8 +20,8 @@ * */ -#ifndef COMMON_CONFIG_FILE_H -#define COMMON_CONFIG_FILE_H +#ifndef COMMON_INI_FILE_H +#define COMMON_INI_FILE_H #include "common/hash-str.h" #include "common/list.h" @@ -34,9 +34,6 @@ class WriteStream; /** * This class allows reading/writing INI style config files. - * It is used by the ConfigManager for storage, but can also - * be used by other code if it needs to read/write custom INI - * files. * * Lines starting with a '#' are ignored (i.e. treated as comments). * Some effort is made to preserve comments, though. @@ -47,10 +44,8 @@ class WriteStream; * from/to files, but of course is not appropriate for fast access. * The main reason is that this class is indeed geared toward doing precisely * that! - * If you need fast access to the game config, use higher level APIs, like the - * one provided by ConfigManager. */ -class ConfigFile { +class INIFile { public: struct KeyValue { String key; @@ -60,12 +55,12 @@ public: typedef List<KeyValue> SectionKeyList; - /** A section in a config file. I.e. corresponds to something like this: + /** A section in a ini file. I.e. corresponds to something like this: * [mySection] * key=value * * Comments are also stored, to keep users happy who like editing their - * config files manually. + * ini files manually. */ struct Section { String name; @@ -82,8 +77,8 @@ public: typedef List<Section> SectionList; public: - ConfigFile(); - ~ConfigFile(); + INIFile(); + ~INIFile(); // TODO: Maybe add a copy constructor etc.? @@ -95,7 +90,7 @@ public: */ static bool isValidName(const String &name); - /** Reset everything stored in this config file. */ + /** Reset everything stored in this ini file. */ void clear(); bool loadFromFile(const String &filename); @@ -127,14 +122,6 @@ private: const Section *getSection(const String §ion) const; }; -/* -- ConfigMan owns a config file -- allow direct access to that config file (for the launcher) -- simplify and unify the regular ConfigMan API in exchange - - -*/ - } // End of namespace Common #endif 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/math.h b/common/math.h index b85ec0d22a..ba137101e4 100644 --- a/common/math.h +++ b/common/math.h @@ -52,14 +52,6 @@ #endif #endif -#ifndef M_SQRT1_2 - #define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ -#endif - -#ifndef M_PI - #define M_PI 3.14159265358979323846 -#endif - #ifndef FLT_MIN #define FLT_MIN 1E-37 #endif 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..67c498df00 100644 --- a/common/module.mk +++ b/common/module.mk @@ -2,7 +2,6 @@ MODULE := common MODULE_OBJS := \ archive.o \ - config-file.o \ config-manager.o \ coroutines.o \ dcl.o \ @@ -10,12 +9,12 @@ MODULE_OBJS := \ error.o \ EventDispatcher.o \ EventMapper.o \ - EventRecorder.o \ file.o \ fs.o \ gui_options.o \ hashmap.o \ iff_container.o \ + ini-file.o \ installshield_cab.o \ language.o \ localization.o \ @@ -36,6 +35,7 @@ MODULE_OBJS := \ translation.o \ unarj.o \ unzip.o \ + ustr.o \ util.o \ winexe.o \ winexe_ne.o \ @@ -51,5 +51,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/rdft.h b/common/rdft.h index 3386940404..76e95c363a 100644 --- a/common/rdft.h +++ b/common/rdft.h @@ -20,7 +20,7 @@ * */ -// Based on eos' (I)RDFT code which is in turn +// Based on xoreos' (I)RDFT code which is in turn // Based upon the (I)RDFT code in FFmpeg // Copyright (c) 2009 Alex Converse <alex dot converse at gmail dot com> @@ -44,6 +44,40 @@ namespace Common { * * Used in engines: * - scumm + * + * + * It has four modes: + * + * Below, n = 1 << bits + * + * (I)DFT_R2C: + * input: + * n real floats + * output: + * n/2 complex floats (stored as real part followed by imag part). + * + * The output represents the first half of the (I)DFT of the input. + * If F is the complex (I)DFT of the input, then + * output[0] = F[0] + i * F[n/2] and + * output[k] = F[k] for k = 1 .. n/2-1. + * Note that F[0] and F[k] are real since the input is real, and + * the remaining values of F can be reconstructed from symmetry if desired. + * + * (I)DFT_C2R: + * input: + * n/2 complex floats + * output: + * n real floats + * + * The input encodes a complex vector x of length n that has the + * required symmetry to have a real (I)DFT: + * x[0] = Re(input[0]) + * x[k] = input[k] for k = 1 .. n/2-1 + * x[n/2] = Im(input[0]) + * x[k] = conj(input[n-k]) for k = n/2+1 .. n-1 + * The output is then the real (I)DFT of x, divided by 2. + * + * TODO: Is this division by 2 intentional? */ class RDFT { diff --git a/common/recorderfile.cpp b/common/recorderfile.cpp new file mode 100644 index 0000000000..7c438cbf69 --- /dev/null +++ b/common/recorderfile.cpp @@ -0,0 +1,716 @@ +/* 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); + + _playbackParseState = kFileStateCheckFormat; +} + +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/scummsys.h b/common/scummsys.h index 291de87dc9..48dffc53a6 100644 --- a/common/scummsys.h +++ b/common/scummsys.h @@ -23,6 +23,10 @@ #ifndef COMMON_SCUMMSYS_H #define COMMON_SCUMMSYS_H +#ifndef __has_feature // Optional of course. + #define __has_feature(x) 0 // Compatibility with non-clang compilers. +#endif + // This is a convenience macro to test whether the compiler used is a GCC // version, which is at least major.minor. #define GCC_ATLEAST(major, minor) (defined(__GNUC__) && (__GNUC__ > (major) || (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor)))) @@ -144,6 +148,63 @@ #endif #endif +// The following math constants are usually defined by the system math.h header, but +// they are not part of the ANSI C++ standards and so can NOT be relied upon to be +// present i.e. when -std=c++11 is passed to GCC, enabling strict ANSI compliance. +// As we rely on these being present, we define them if they are not set. + +#ifndef M_E + #define M_E 2.7182818284590452354 /* e */ +#endif + +#ifndef M_LOG2E + #define M_LOG2E 1.4426950408889634074 /* log_2 e */ +#endif + +#ifndef M_LOG10E + #define M_LOG10E 0.43429448190325182765 /* log_10 e */ +#endif + +#ifndef M_LN2 + #define M_LN2 0.69314718055994530942 /* log_e 2 */ +#endif + +#ifndef M_LN10 + #define M_LN10 2.30258509299404568402 /* log_e 10 */ +#endif + +#ifndef M_PI + #define M_PI 3.14159265358979323846 /* pi */ +#endif + +#ifndef M_PI_2 + #define M_PI_2 1.57079632679489661923 /* pi/2 */ +#endif + +#ifndef M_PI_4 + #define M_PI_4 0.78539816339744830962 /* pi/4 */ +#endif + +#ifndef M_1_PI + #define M_1_PI 0.31830988618379067154 /* 1/pi */ +#endif + +#ifndef M_2_PI + #define M_2_PI 0.63661977236758134308 /* 2/pi */ +#endif + +#ifndef M_2_SQRTPI + #define M_2_SQRTPI 1.12837916709551257390 /* 2/sqrt(pi) */ +#endif + +#ifndef M_SQRT2 + #define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ +#endif + +#ifndef M_SQRT1_2 + #define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ +#endif + // Include our C++11 compatability header for pre-C++11 compilers. #if __cplusplus < 201103L #include "common/c++11-compat.h" @@ -344,6 +405,8 @@ typedef unsigned int uint32; typedef signed int int32; typedef unsigned int uint; + typedef signed long long int64; + typedef unsigned long long uint64; #endif diff --git a/common/sinetables.cpp b/common/sinetables.cpp index a6ec99469d..7338166d39 100644 --- a/common/sinetables.cpp +++ b/common/sinetables.cpp @@ -34,15 +34,15 @@ SineTable::SineTable(int bitPrecision) { int m = 1 << _bitPrecision; double freq = 2 * M_PI / m; - _table = new float[m]; + _table = new float[m / 2]; - // Table contains sin(2*pi*x/n) for 0<=x<=n/4, - // followed by its reverse - for (int i = 0; i <= m / 4; i++) + // Table contains sin(2*pi*i/m) for 0<=i<m/4, + // followed by m/2<=i<3m/4 + for (int i = 0; i < m / 4; i++) _table[i] = sin(i * freq); - for (int i = 1; i < m / 4; i++) - _table[m / 2 - i] = _table[i]; + for (int i = 0; i < m / 4; i++) + _table[m / 4 + i] = -_table[i]; } SineTable::~SineTable() { diff --git a/common/sinetables.h b/common/sinetables.h index 16e203f26d..3489663661 100644 --- a/common/sinetables.h +++ b/common/sinetables.h @@ -37,6 +37,13 @@ public: /** * Get pointer to table + * + * This table contains 2^bitPrecision/2 entries. + * The layout of this table is as follows: + * - Entries 0 up to (excluding) 2^bitPrecision/4: + * sin(0) till (excluding) sin(1/2*pi) + * - Entries 2^bitPrecision/4 up to (excluding) 2^bitPrecision/2: + * sin(pi) till (excluding) sin(3/2*pi) */ const float *getTable() { return _table; } 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..ea2db1d1d6 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); @@ -229,6 +234,13 @@ public: static String vformat(const char *fmt, va_list args); public: + typedef char value_type; + /** + * Unsigned version of the underlying type. This can be used to cast + * individual string characters to bigger integer types without sign + * extension happening. + */ + typedef unsigned char unsigned_type; typedef char * iterator; typedef const char * const_iterator; 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/unarj.cpp b/common/unarj.cpp index fe3c17a2ac..e8aed7cbd1 100644 --- a/common/unarj.cpp +++ b/common/unarj.cpp @@ -95,6 +95,12 @@ class ArjDecoder { public: ArjDecoder(const ArjHeader *hdr) { _compsize = hdr->compSize; + _compressed = 0; + _outstream = 0; + _bitbuf = 0; + _bytebuf = 0; + _bitcount = 0; + _blocksize = 0; } ~ArjDecoder() { @@ -112,7 +118,6 @@ public: uint16 _bitbuf; uint16 _bytebuf; int32 _compsize; - byte _subbitbuf; int _bitcount; void init_getbits(); @@ -132,9 +137,6 @@ public: private: byte _ntext[ARJ_FDICSIZ]; - int16 _getlen; - int16 _getbuf; - uint16 _left[2 * ARJ_NC - 1]; uint16 _right[2 * ARJ_NC - 1]; byte _c_len[ARJ_NC]; @@ -656,7 +658,6 @@ void ArjDecoder::decode_f(int32 origsize) { init_getbits(); ncount = 0; - _getlen = _getbuf = 0; r = 0; while (ncount < (uint32)origsize) { diff --git a/common/ustr.cpp b/common/ustr.cpp new file mode 100644 index 0000000000..fbc831cb56 --- /dev/null +++ b/common/ustr.cpp @@ -0,0 +1,329 @@ +/* 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/ustr.h" +#include "common/memorypool.h" +#include "common/util.h" + +namespace Common { + +extern MemoryPool *g_refCountPool; + +static uint32 computeCapacity(uint32 len) { + // By default, for the capacity we use the next multiple of 32 + return ((len + 32 - 1) & ~0x1F); +} + +U32String::U32String(const value_type *str) : _size(0), _str(_storage) { + if (str == 0) { + _storage[0] = 0; + _size = 0; + } else { + uint32 len = 0; + const value_type *s = str; + while (*s++) { + ++len; + } + initWithCStr(str, len); + } +} + +U32String::U32String(const value_type *str, uint32 len) : _size(0), _str(_storage) { + initWithCStr(str, len); +} + +U32String::U32String(const value_type *beginP, const value_type *endP) : _size(0), _str(_storage) { + assert(endP >= beginP); + initWithCStr(beginP, endP - beginP); +} + +U32String::U32String(const U32String &str) + : _size(str._size) { + if (str.isStorageIntern()) { + // String in internal storage: just copy it + memcpy(_storage, str._storage, _builtinCapacity * sizeof(value_type)); + _str = _storage; + } else { + // String in external storage: use refcount mechanism + str.incRefCount(); + _extern._refCount = str._extern._refCount; + _extern._capacity = str._extern._capacity; + _str = str._str; + } + assert(_str != 0); +} + +U32String::~U32String() { + decRefCount(_extern._refCount); +} + +U32String &U32String::operator=(const U32String &str) { + if (&str == this) + return *this; + + if (str.isStorageIntern()) { + decRefCount(_extern._refCount); + _size = str._size; + _str = _storage; + memcpy(_str, str._str, (_size + 1) * sizeof(value_type)); + } else { + str.incRefCount(); + decRefCount(_extern._refCount); + + _extern._refCount = str._extern._refCount; + _extern._capacity = str._extern._capacity; + _size = str._size; + _str = str._str; + } + + return *this; +} + +U32String &U32String::operator+=(const U32String &str) { + if (&str == this) { + return operator+=(U32String(str)); + } + + int len = str._size; + if (len > 0) { + ensureCapacity(_size + len, true); + + memcpy(_str + _size, str._str, (len + 1) * sizeof(value_type)); + _size += len; + } + return *this; +} + +U32String &U32String::operator+=(value_type c) { + ensureCapacity(_size + 1, true); + + _str[_size++] = c; + _str[_size] = 0; + + return *this; +} + +bool U32String::equals(const U32String &x) const { + if (this == &x || _str == x._str) { + return true; + } + + if (x.size() != _size) { + return false; + } + + return !memcmp(_str, x._str, _size * sizeof(value_type)); +} + +bool U32String::contains(value_type x) const { + for (uint32 i = 0; i < _size; ++i) { + if (_str[i] == x) { + return true; + } + } + + return false; +} + +void U32String::deleteChar(uint32 p) { + assert(p < _size); + + makeUnique(); + while (p++ < _size) + _str[p - 1] = _str[p]; + _size--; +} + +void U32String::clear() { + decRefCount(_extern._refCount); + + _size = 0; + _str = _storage; + _storage[0] = 0; +} + +void U32String::toLowercase() { + makeUnique(); + for (uint32 i = 0; i < _size; ++i) { + if (_str[i] < 128) { + _str[i] = tolower(_str[i]); + } + } +} + +void U32String::toUppercase() { + makeUnique(); + for (uint32 i = 0; i < _size; ++i) { + if (_str[i] < 128) { + _str[i] = toupper(_str[i]); + } + } +} + +uint32 U32String::find(const U32String &str, uint32 pos) const { + if (pos >= _size) { + return npos; + } + + const value_type *strP = str.c_str(); + + for (const_iterator cur = begin() + pos; *cur; ++cur) { + uint i = 0; + while (true) { + if (!strP[i]) { + return cur - begin(); + } + + if (cur[i] != strP[i]) { + break; + } + + ++i; + } + } + + return npos; +} + +void U32String::makeUnique() { + ensureCapacity(_size, true); +} + +void U32String::ensureCapacity(uint32 new_size, bool keep_old) { + bool isShared; + uint32 curCapacity, newCapacity; + value_type *newStorage; + int *oldRefCount = _extern._refCount; + + if (isStorageIntern()) { + isShared = false; + curCapacity = _builtinCapacity; + } else { + isShared = (oldRefCount && *oldRefCount > 1); + curCapacity = _extern._capacity; + } + + // Special case: If there is enough space, and we do not share + // the storage, then there is nothing to do. + if (!isShared && new_size < curCapacity) + return; + + if (isShared && new_size < _builtinCapacity) { + // We share the storage, but there is enough internal storage: Use that. + newStorage = _storage; + newCapacity = _builtinCapacity; + } else { + // We need to allocate storage on the heap! + + // Compute a suitable new capacity limit + // If the current capacity is sufficient we use the same capacity + if (new_size < curCapacity) + newCapacity = curCapacity; + else + newCapacity = MAX(curCapacity * 2, computeCapacity(new_size+1)); + + // Allocate new storage + newStorage = new value_type[newCapacity]; + assert(newStorage); + } + + // Copy old data if needed, elsewise reset the new storage. + if (keep_old) { + assert(_size < newCapacity); + memcpy(newStorage, _str, (_size + 1) * sizeof(value_type)); + } else { + _size = 0; + newStorage[0] = 0; + } + + // Release hold on the old storage ... + decRefCount(oldRefCount); + + // ... in favor of the new storage + _str = newStorage; + + if (!isStorageIntern()) { + // Set the ref count & capacity if we use an external storage. + // It is important to do this *after* copying any old content, + // else we would override data that has not yet been copied! + _extern._refCount = 0; + _extern._capacity = newCapacity; + } +} + +void U32String::incRefCount() const { + assert(!isStorageIntern()); + if (_extern._refCount == 0) { + if (g_refCountPool == 0) { + g_refCountPool = new MemoryPool(sizeof(int)); + assert(g_refCountPool); + } + + _extern._refCount = (int *)g_refCountPool->allocChunk(); + *_extern._refCount = 2; + } else { + ++(*_extern._refCount); + } +} + +void U32String::decRefCount(int *oldRefCount) { + if (isStorageIntern()) + return; + + if (oldRefCount) { + --(*oldRefCount); + } + if (!oldRefCount || *oldRefCount <= 0) { + // The ref count reached zero, so we free the string storage + // and the ref count storage. + if (oldRefCount) { + assert(g_refCountPool); + g_refCountPool->freeChunk(oldRefCount); + } + delete[] _str; + + // Even though _str points to a freed memory block now, + // we do not change its value, because any code that calls + // decRefCount will have to do this afterwards anyway. + } +} + +void U32String::initWithCStr(const value_type *str, uint32 len) { + assert(str); + + _storage[0] = 0; + + _size = len; + + if (len >= _builtinCapacity) { + // Not enough internal storage, so allocate more + _extern._capacity = computeCapacity(len+1); + _extern._refCount = 0; + _str = new value_type[_extern._capacity]; + assert(_str != 0); + } + + // Copy the string into the storage area + memmove(_str, str, len * sizeof(value_type)); + _str[len] = 0; +} + +} // End of namespace Common diff --git a/common/ustr.h b/common/ustr.h new file mode 100644 index 0000000000..ab9dac70de --- /dev/null +++ b/common/ustr.h @@ -0,0 +1,194 @@ +/* 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_USTR_H +#define COMMON_USTR_H + +#include "common/scummsys.h" + +namespace Common { + +/** + * Very simple string class for UTF-32 strings in ScummVM. The main intention + * behind this class is to feature a simple way of displaying UTF-32 strings + * through the Graphics::Font API. + * + * Please note that operations like equals, deleteCharacter, toUppercase, etc. + * are only very simplified convenience operations. They might not fully work + * as you would expect for a proper UTF-32 string class. + * + * The presence of \0 characters in the string will cause undefined + * behavior in some operations. + */ +class U32String { +public: + static const uint32 npos = 0xFFFFFFFF; + + typedef uint32 value_type; + typedef uint32 unsigned_type; +private: + /** + * The size of the internal storage. Increasing this means less heap + * allocations are needed, at the cost of more stack memory usage, + * and of course lots of wasted memory. + */ + static const uint32 _builtinCapacity = 32; + + /** + * Length of the string. + */ + uint32 _size; + + /** + * Pointer to the actual string storage. Either points to _storage, + * or to a block allocated on the heap via malloc. + */ + value_type *_str; + + + union { + /** + * Internal string storage. + */ + value_type _storage[_builtinCapacity]; + /** + * External string storage data -- the refcounter, and the + * capacity of the string _str points to. + */ + struct { + mutable int *_refCount; + uint32 _capacity; + } _extern; + }; + + inline bool isStorageIntern() const { + return _str == _storage; + } + +public: + /** Construct a new empty string. */ + U32String() : _size(0), _str(_storage) { _storage[0] = 0; } + + /** Construct a new string from the given NULL-terminated C string. */ + explicit U32String(const value_type *str); + + /** Construct a new string containing exactly len characters read from address str. */ + U32String(const value_type *str, uint32 len); + + /** Construct a new string containing the characters between beginP (including) and endP (excluding). */ + U32String(const value_type *beginP, const value_type *endP); + + /** Construct a copy of the given string. */ + U32String(const U32String &str); + + ~U32String(); + + U32String &operator=(const U32String &str); + U32String &operator+=(const U32String &str); + U32String &operator+=(value_type c); + + /** + * Equivalence comparison operator. + * @see equals + */ + bool operator==(const U32String &x) const { return equals(x); } + + /** + * Compares whether two U32String are the same based on memory comparison. + * This does *not* do comparison based on canonical equivalence. + */ + bool equals(const U32String &x) const; + + bool contains(value_type x) const; + + inline const value_type *c_str() const { return _str; } + inline uint32 size() const { return _size; } + + inline bool empty() const { return (_size == 0); } + + value_type operator[](int idx) const { + assert(_str && idx >= 0 && idx < (int)_size); + return _str[idx]; + } + + /** + * Removes the value at position p from the string. + * Using this on decomposed characters will not remove the whole + * character! + */ + void deleteChar(uint32 p); + + /** Clears the string, making it empty. */ + void clear(); + + /** + * Convert all characters in the string to lowercase. + * + * Be aware that this only affects the case of ASCII characters. All + * other characters will not be touched at all. + */ + void toLowercase(); + + /** + * Convert all characters in the string to uppercase. + * + * Be aware that this only affects the case of ASCII characters. All + * other characters will not be touched at all. + */ + void toUppercase(); + + uint32 find(const U32String &str, uint32 pos = 0) const; + + typedef value_type * iterator; + typedef const value_type * const_iterator; + + iterator begin() { + // Since the user could potentially + // change the string via the returned + // iterator we have to assure we are + // pointing to a unique storage. + makeUnique(); + + return _str; + } + + iterator end() { + return begin() + size(); + } + + const_iterator begin() const { + return _str; + } + + const_iterator end() const { + return begin() + size(); + } +private: + void makeUnique(); + void ensureCapacity(uint32 new_size, bool keep_old); + void incRefCount() const; + void decRefCount(int *oldRefCount); + void initWithCStr(const value_type *str, uint32 len); +}; + +} // End of namespace Common + +#endif diff --git a/common/util.h b/common/util.h index 4ca1c42929..392ced1ffe 100644 --- a/common/util.h +++ b/common/util.h @@ -41,10 +41,10 @@ #undef MAX #endif -template<typename T> inline T ABS (T x) { return (x>=0) ? x : -x; } -template<typename T> inline T MIN (T a, T b) { return (a<b) ? a : b; } -template<typename T> inline T MAX (T a, T b) { return (a>b) ? a : b; } -template<typename T> inline T CLIP (T v, T amin, T amax) +template<typename T> inline T ABS(T x) { return (x >= 0) ? x : -x; } +template<typename T> inline T MIN(T a, T b) { return (a < b) ? a : b; } +template<typename T> inline T MAX(T a, T b) { return (a > b) ? a : b; } +template<typename T> inline T CLIP(T v, T amin, T amax) { if (v < amin) return amin; else if (v > amax) return amax; else return v; } /** diff --git a/common/winexe.cpp b/common/winexe.cpp index 7cfc140452..877ab6baa1 100644 --- a/common/winexe.cpp +++ b/common/winexe.cpp @@ -73,7 +73,7 @@ String WinResourceID::toString() const { if (_idType == kIDTypeString) return _name; else if (_idType == kIDTypeNumerical) - return String::format("%08x", _id); + return String::format("0x%08x", _id); return ""; } diff --git a/common/winexe_ne.cpp b/common/winexe_ne.cpp index 8690f6795b..8ab7e707bb 100644 --- a/common/winexe_ne.cpp +++ b/common/winexe_ne.cpp @@ -187,8 +187,8 @@ uint32 NEResources::getResourceTableOffset() { static const char *s_resTypeNames[] = { "", "cursor", "bitmap", "icon", "menu", "dialog", "string", "font_dir", "font", "accelerator", "rc_data", "msg_table", - "group_cursor", "group_icon", "version", "dlg_include", - "plug_play", "vxd", "ani_cursor", "ani_icon", "html", + "group_cursor", "", "group_icon", "", "version", "dlg_include", + "", "plug_play", "vxd", "ani_cursor", "ani_icon", "html", "manifest" }; @@ -200,9 +200,16 @@ bool NEResources::readResourceTable(uint32 offset) { return false; uint32 align = 1 << _exe->readUint16LE(); - uint16 typeID = _exe->readUint16LE(); + while (typeID != 0) { + // High bit of the type means integer type + WinResourceID type; + if (typeID & 0x8000) + type = typeID & 0x7FFF; + else + type = getResourceString(*_exe, offset + typeID); + uint16 resCount = _exe->readUint16LE(); _exe->skip(4); // reserved @@ -218,17 +225,18 @@ bool NEResources::readResourceTable(uint32 offset) { res.handle = _exe->readUint16LE(); res.usage = _exe->readUint16LE(); - res.type = typeID; + res.type = type; - if ((id & 0x8000) == 0) - res.id = getResourceString(*_exe, offset + id); - else + // High bit means integer type + if (id & 0x8000) res.id = id & 0x7FFF; + else + res.id = getResourceString(*_exe, offset + id); - if (typeID & 0x8000 && ((typeID & 0x7FFF) < ARRAYSIZE(s_resTypeNames))) + 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()); else - debug(2, "Found resource %04x %s", typeID, res.id.toString().c_str()); + debug(2, "Found resource %s %s", type.toString().c_str(), res.id.toString().c_str()); _resources.push_back(res); } @@ -257,7 +265,7 @@ String NEResources::getResourceString(SeekableReadStream &exe, uint32 offset) { return string; } -const NEResources::Resource *NEResources::findResource(uint16 type, WinResourceID id) const { +const NEResources::Resource *NEResources::findResource(const WinResourceID &type, const WinResourceID &id) const { for (List<Resource>::const_iterator it = _resources.begin(); it != _resources.end(); ++it) if (it->type == type && it->id == id) return &*it; @@ -265,7 +273,7 @@ const NEResources::Resource *NEResources::findResource(uint16 type, WinResourceI return 0; } -SeekableReadStream *NEResources::getResource(uint16 type, WinResourceID id) { +SeekableReadStream *NEResources::getResource(const WinResourceID &type, const WinResourceID &id) { const Resource *res = findResource(type, id); if (!res) @@ -275,7 +283,7 @@ SeekableReadStream *NEResources::getResource(uint16 type, WinResourceID id) { return _exe->readStream(res->size); } -const Array<WinResourceID> NEResources::getIDList(uint16 type) const { +const Array<WinResourceID> NEResources::getIDList(const WinResourceID &type) const { Array<WinResourceID> idArray; for (List<Resource>::const_iterator it = _resources.begin(); it != _resources.end(); ++it) diff --git a/common/winexe_ne.h b/common/winexe_ne.h index 4a1b2343df..3f50b5cc54 100644 --- a/common/winexe_ne.h +++ b/common/winexe_ne.h @@ -34,27 +34,27 @@ class SeekableReadStream; /** The default Windows resources. */ enum NEResourceType { - kNECursor = 0x8001, - kNEBitmap = 0x8002, - kNEIcon = 0x8003, - kNEMenu = 0x8004, - kNEDialog = 0x8005, - kNEString = 0x8006, - kNEFontDir = 0x8007, - kNEFont = 0x8008, - kNEAccelerator = 0x8009, - kNERCData = 0x800A, - kNEMessageTable = 0x800B, - kNEGroupCursor = 0x800C, - kNEGroupIcon = 0x800D, - kNEVersion = 0x8010, - kNEDlgInclude = 0x8011, - kNEPlugPlay = 0x8013, - kNEVXD = 0x8014, - kNEAniCursor = 0x8015, - kNEAniIcon = 0x8016, - kNEHTML = 0x8017, - kNEManifest = 0x8018 + kNECursor = 0x01, + kNEBitmap = 0x02, + kNEIcon = 0x03, + kNEMenu = 0x04, + kNEDialog = 0x05, + kNEString = 0x06, + kNEFontDir = 0x07, + kNEFont = 0x08, + kNEAccelerator = 0x09, + kNERCData = 0x0A, + kNEMessageTable = 0x0B, + kNEGroupCursor = 0x0C, + kNEGroupIcon = 0x0E, + kNEVersion = 0x10, + kNEDlgInclude = 0x11, + kNEPlugPlay = 0x13, + kNEVXD = 0x14, + kNEAniCursor = 0x15, + kNEAniIcon = 0x16, + kNEHTML = 0x17, + kNEManifest = 0x18 }; /** @@ -81,17 +81,17 @@ public: bool loadFromEXE(SeekableReadStream *stream); /** Return a list of resources for a given type. */ - const Array<WinResourceID> getIDList(uint16 type) const; + const Array<WinResourceID> getIDList(const WinResourceID &type) const; /** Return a stream to the specified resource (or 0 if non-existent). */ - SeekableReadStream *getResource(uint16 type, WinResourceID id); + SeekableReadStream *getResource(const WinResourceID &type, const WinResourceID &id); private: /** A resource. */ struct Resource { WinResourceID id; - uint16 type; ///< Type of the resource. + WinResourceID type; ///< Type of the resource. uint32 offset; ///< Offset within the EXE. uint32 size; ///< Size of the data. @@ -112,7 +112,7 @@ private: bool readResourceTable(uint32 offset); /** Find a specific resource. */ - const Resource *findResource(uint16 type, WinResourceID id) const; + const Resource *findResource(const WinResourceID &type, const WinResourceID &id) const; /** Read a resource string. */ static String getResourceString(SeekableReadStream &exe, uint32 offset); diff --git a/common/zlib.cpp b/common/zlib.cpp index 920338e57e..f1a298a9f1 100644 --- a/common/zlib.cpp +++ b/common/zlib.cpp @@ -27,6 +27,7 @@ #include "common/ptr.h" #include "common/util.h" #include "common/stream.h" +#include "common/textconsole.h" #if defined(USE_ZLIB) #ifdef __SYMBIAN32__ @@ -158,10 +159,11 @@ protected: uint32 _pos; uint32 _origSize; bool _eos; + bool _shownBackwardSeekingWarning; public: - GZipReadStream(SeekableReadStream *w, uint32 knownSize = 0) : _wrapped(w), _stream() { + GZipReadStream(SeekableReadStream *w, uint32 knownSize = 0) : _wrapped(w), _stream(), _shownBackwardSeekingWarning(false) { assert(w != 0); // Verify file header is correct @@ -241,13 +243,17 @@ public: } bool seek(int32 offset, int whence = SEEK_SET) { int32 newPos = 0; - assert(whence != SEEK_END); // SEEK_END not supported switch (whence) { case SEEK_SET: newPos = offset; break; case SEEK_CUR: newPos = _pos + offset; + break; + case SEEK_END: + // NOTE: This can be an expensive operation (see below). + newPos = size() + offset; + break; } assert(newPos >= 0); @@ -256,9 +262,15 @@ public: // To search backward, we have to restart the whole decompression // from the start of the file. A rather wasteful operation, best // to avoid it. :/ -#if DEBUG - warning("Backward seeking in GZipReadStream detected"); -#endif + + if (!_shownBackwardSeekingWarning) { + // We only throw this warning once per stream, to avoid + // getting the console swarmed with warnings when consecutive + // seeks are made. + warning("Backward seeking in GZipReadStream detected"); + _shownBackwardSeekingWarning = true; + } + _pos = 0; _wrapped->seek(0, SEEK_SET); _zlibErr = inflateReset(&_stream); |