aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/voyeur/animation.cpp485
-rw-r--r--engines/voyeur/animation.h196
-rw-r--r--engines/voyeur/configure.engine4
-rw-r--r--engines/voyeur/data.cpp359
-rw-r--r--engines/voyeur/data.h231
-rw-r--r--engines/voyeur/debugger.cpp148
-rw-r--r--engines/voyeur/debugger.h72
-rw-r--r--engines/voyeur/detection.cpp182
-rw-r--r--engines/voyeur/detection_tables.h42
-rw-r--r--engines/voyeur/events.cpp624
-rw-r--r--engines/voyeur/events.h153
-rw-r--r--engines/voyeur/files.cpp1659
-rw-r--r--engines/voyeur/files.h650
-rw-r--r--engines/voyeur/files_threads.cpp1738
-rw-r--r--engines/voyeur/graphics.cpp1065
-rw-r--r--engines/voyeur/graphics.h134
-rw-r--r--engines/voyeur/module.mk23
-rw-r--r--engines/voyeur/sound.cpp85
-rw-r--r--engines/voyeur/sound.h60
-rw-r--r--engines/voyeur/staticres.cpp142
-rw-r--r--engines/voyeur/staticres.h63
-rw-r--r--engines/voyeur/voyeur.cpp919
-rw-r--r--engines/voyeur/voyeur.h321
-rw-r--r--engines/voyeur/voyeur_game.cpp1423
24 files changed, 10778 insertions, 0 deletions
diff --git a/engines/voyeur/animation.cpp b/engines/voyeur/animation.cpp
new file mode 100644
index 0000000000..f81573f733
--- /dev/null
+++ b/engines/voyeur/animation.cpp
@@ -0,0 +1,485 @@
+/* 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 "voyeur/animation.h"
+#include "voyeur/staticres.h"
+#include "voyeur/voyeur.h"
+#include "common/endian.h"
+#include "common/memstream.h"
+#include "common/system.h"
+#include "audio/decoders/raw.h"
+#include "graphics/surface.h"
+
+namespace Voyeur {
+
+// Number of audio frames to keep audio track topped up when playing back video
+#define SOUND_FRAMES_READAHEAD 3
+
+RL2Decoder::RL2Decoder(Audio::Mixer::SoundType soundType) : _soundType(soundType) {
+ _paletteStart = 0;
+ _fileStream = nullptr;
+ _soundFrameNumber = -1;
+}
+
+RL2Decoder::~RL2Decoder() {
+ close();
+}
+
+bool RL2Decoder::loadVideo(int videoId) {
+ Common::String filename = Common::String::format("%s.rl2",
+ ::Voyeur::SZ_FILENAMES[videoId * 2]);
+ return loadFile(filename);
+}
+
+bool RL2Decoder::loadFile(const Common::String &file, bool palFlag) {
+ bool result = VideoDecoder::loadFile(file);
+ _paletteStart = palFlag ? 0 : 128;
+ return result;
+}
+
+bool RL2Decoder::loadStream(Common::SeekableReadStream *stream) {
+ close();
+
+ // Load basic file information
+ _fileStream = stream;
+ _header.load(stream);
+ _paletteStart = 0;
+
+ // Check RL2 magic number
+ if (!_header.isValid()) {
+ warning("RL2Decoder::loadStream(): attempted to load non-RL2 data (0x%08X)", _header._signature);
+ return false;
+ }
+
+ // Add an audio track if sound is present
+ _audioTrack = nullptr;
+ if (_header._soundRate) {
+ _audioTrack = new RL2AudioTrack(_header, stream, _soundType);
+ addTrack(_audioTrack);
+ }
+
+ // Create a video track
+ _videoTrack = new RL2VideoTrack(_header, _audioTrack, stream);
+ addTrack(_videoTrack);
+
+ // Load the offset/sizes of the video's audio data
+ _soundFrames.reserve(_header._numFrames);
+ for (int frameNumber = 0; frameNumber < _header._numFrames; ++frameNumber) {
+ int offset = _header._frameOffsets[frameNumber];
+ int size = _header._frameSoundSizes[frameNumber];
+
+ _soundFrames.push_back(SoundFrame(offset, size));
+ }
+
+ return true;
+}
+
+const Common::List<Common::Rect> *RL2Decoder::getDirtyRects() const {
+ if (_videoTrack)
+ return _videoTrack->getDirtyRects();
+
+ return 0;
+}
+
+void RL2Decoder::clearDirtyRects() {
+ if (_videoTrack)
+ _videoTrack->clearDirtyRects();
+}
+
+void RL2Decoder::copyDirtyRectsToBuffer(uint8 *dst, uint pitch) {
+ if (_videoTrack)
+ _videoTrack->copyDirtyRectsToBuffer(dst, pitch);
+}
+
+void RL2Decoder::readNextPacket() {
+ int frameNumber = getCurFrame();
+ RL2AudioTrack *audioTrack = getAudioTrack();
+
+ // Handle queueing sound data
+ if (_soundFrameNumber == -1)
+ _soundFrameNumber = (frameNumber == -1) ? 0 : frameNumber;
+
+ while (audioTrack->numQueuedStreams() < SOUND_FRAMES_READAHEAD &&
+ (_soundFrameNumber < (int)_soundFrames.size())) {
+ _fileStream->seek(_soundFrames[_soundFrameNumber]._offset);
+ audioTrack->queueSound(_fileStream, _soundFrames[_soundFrameNumber]._size);
+ ++_soundFrameNumber;
+ }
+}
+
+bool RL2Decoder::seekIntern(const Audio::Timestamp &where) {
+ _soundFrameNumber = -1;
+ return VideoDecoder::seekIntern(where);
+}
+
+void RL2Decoder::close() {
+ VideoDecoder::close();
+ delete _fileStream;
+ _fileStream = nullptr;
+ _soundFrameNumber = -1;
+}
+
+/*------------------------------------------------------------------------*/
+
+RL2Decoder::SoundFrame::SoundFrame(int offset, int size) {
+ _offset = offset;
+ _size = size;
+}
+
+/*------------------------------------------------------------------------*/
+
+RL2Decoder::RL2FileHeader::RL2FileHeader() {
+ _frameOffsets = nullptr;
+ _frameSoundSizes = nullptr;
+}
+
+RL2Decoder::RL2FileHeader::~RL2FileHeader() {
+ delete[] _frameOffsets;
+ delete[] _frameSoundSizes;
+}
+
+void RL2Decoder::RL2FileHeader::load(Common::SeekableReadStream *stream) {
+ stream->seek(0);
+
+ _form = stream->readUint32LE();
+ _backSize = stream->readUint32LE();
+ _signature = stream->readUint32BE();
+
+ if (!isValid())
+ return;
+
+ _dataSize = stream->readUint32LE();
+ _numFrames = stream->readUint32LE();
+ _method = stream->readUint16LE();
+ _soundRate = stream->readUint16LE();
+ _rate = stream->readUint16LE();
+ _channels = stream->readUint16LE();
+ _defSoundSize = stream->readUint16LE();
+ _videoBase = stream->readUint16LE();
+ _colorCount = stream->readUint32LE();
+ assert(_colorCount <= 256);
+
+ stream->read(_palette, 768);
+
+ // Skip over background frame, if any, and the list of overall frame sizes (which we don't use)
+ stream->skip(_backSize + 4 * _numFrames);
+
+ // Load frame offsets
+ delete[] _frameOffsets;
+ _frameOffsets = new uint32[_numFrames];
+ for (int i = 0; i < _numFrames; ++i)
+ _frameOffsets[i] = stream->readUint32LE();
+
+ // Load the size of the sound portion of each frame
+ delete[] _frameSoundSizes;
+ _frameSoundSizes = new int[_numFrames];
+ for (int i = 0; i < _numFrames; ++i)
+ _frameSoundSizes[i] = stream->readUint32LE() & 0xffff;
+}
+
+bool RL2Decoder::RL2FileHeader::isValid() const {
+ return _signature == MKTAG('R','L','V','2') || _signature == MKTAG('R','L','V','3');
+}
+
+Common::Rational RL2Decoder::RL2FileHeader::getFrameRate() const {
+ return (_soundRate > 0) ? Common::Rational(_rate, _defSoundSize) :
+ Common::Rational(11025, 1103);
+}
+
+/*------------------------------------------------------------------------*/
+
+RL2Decoder::RL2VideoTrack::RL2VideoTrack(const RL2FileHeader &header, RL2AudioTrack *audioTrack,
+ Common::SeekableReadStream *stream):
+ _header(header), _audioTrack(audioTrack), _fileStream(stream) {
+
+ // Set up surfaces
+ _surface = new Graphics::Surface();
+ _surface->create(320, 200, Graphics::PixelFormat::createFormatCLUT8());
+
+ _hasBackFrame = header._backSize != 0;
+
+ _backSurface = NULL;
+ if (_hasBackFrame)
+ initBackSurface();
+
+ _videoBase = header._videoBase;
+ _dirtyPalette = header._colorCount > 0;
+
+ _curFrame = -1;
+ _initialFrame = true;
+}
+
+RL2Decoder::RL2VideoTrack::~RL2VideoTrack() {
+ // Free surfaces
+ _surface->free();
+ delete _surface;
+ if (_backSurface) {
+ _backSurface->free();
+ delete _backSurface;
+ }
+}
+
+void RL2Decoder::RL2VideoTrack::initBackSurface() {
+ _backSurface = new Graphics::Surface();
+ _backSurface->create(320, 200, Graphics::PixelFormat::createFormatCLUT8());
+}
+
+bool RL2Decoder::RL2VideoTrack::seek(const Audio::Timestamp &time) {
+ int frame = getFrameAtTime(time);
+
+ if (frame < 0 || frame >= _header._numFrames)
+ return false;
+
+ _curFrame = frame;
+ return true;
+}
+
+uint16 RL2Decoder::RL2VideoTrack::getWidth() const {
+ return _surface->w;
+}
+
+uint16 RL2Decoder::RL2VideoTrack::getHeight() const {
+ return _surface->h;
+}
+
+Graphics::PixelFormat RL2Decoder::RL2VideoTrack::getPixelFormat() const {
+ return _surface->format;
+}
+
+const Graphics::Surface *RL2Decoder::RL2VideoTrack::decodeNextFrame() {
+ if (_initialFrame && _hasBackFrame) {
+ // Read in the initial background frame
+ _fileStream->seek(0x324);
+ rl2DecodeFrameWithoutTransparency(0);
+
+ Common::copy((byte *)_surface->getPixels(), (byte *)_surface->getPixels() + (320 * 200),
+ (byte *)_backSurface->getPixels());
+ _dirtyRects.push_back(Common::Rect(0, 0, _surface->w, _surface->h));
+ _initialFrame = false;
+ }
+
+ // Move to the next frame data
+ _fileStream->seek(_header._frameOffsets[++_curFrame]);
+
+ // If there's any sound data, pass it to the audio track
+ _fileStream->seek(_header._frameSoundSizes[_curFrame], SEEK_CUR);
+
+ // Decode the graphic data using the appropriate method depending on whether the animation
+ // has a background or just raw frames without any background transparency
+ if (_backSurface) {
+ rl2DecodeFrameWithTransparency(_videoBase);
+ } else {
+ rl2DecodeFrameWithoutTransparency(_videoBase);
+ }
+
+ return _surface;
+}
+
+void RL2Decoder::RL2VideoTrack::copyDirtyRectsToBuffer(uint8 *dst, uint pitch) {
+ for (Common::List<Common::Rect>::const_iterator it = _dirtyRects.begin(); it != _dirtyRects.end(); ++it) {
+ for (int y = (*it).top; y < (*it).bottom; ++y) {
+ const int x = (*it).left;
+ memcpy(dst + y * pitch + x, (byte *)_surface->getPixels() + y * getWidth() + x, (*it).right - x);
+ }
+ }
+
+ clearDirtyRects();
+}
+
+void RL2Decoder::RL2VideoTrack::copyFrame(uint8 *data) {
+ memcpy((byte *)_surface->getPixels(), data, getWidth() * getHeight());
+
+ // Redraw
+ _dirtyRects.clear();
+ _dirtyRects.push_back(Common::Rect(0, 0, getWidth(), getHeight()));
+}
+
+void RL2Decoder::RL2VideoTrack::rl2DecodeFrameWithoutTransparency(int screenOffset) {
+ if (screenOffset == -1)
+ screenOffset = _videoBase;
+ int frameSize = _surface->w * _surface->h - screenOffset;
+ byte *destP = (byte *)_surface->getPixels();
+
+ // Main frame decode loop
+ byte nextByte;
+ for (;;) {
+ nextByte = _fileStream->readByte();
+
+ if (nextByte < 0x80) {
+ // Simple byte to copy to output
+ assert(frameSize > 0);
+ *destP++ = nextByte;
+ --frameSize;
+ } else if (nextByte > 0x80) {
+ // Lower 7 bits a run length for the following byte
+ int runLength = _fileStream->readByte();
+ runLength = MIN(runLength, frameSize);
+
+ Common::fill(destP, destP + runLength, nextByte & 0x7f);
+ destP += runLength;
+ frameSize -= runLength;
+ } else {
+ // Follow byte run length for zeroes. If zero, indicates end of image
+ int runLength = _fileStream->readByte();
+ if (runLength == 0)
+ break;
+
+ runLength = MIN(runLength, frameSize);
+ Common::fill(destP, destP + runLength, 0);
+ destP += runLength;
+ frameSize -= runLength;
+ }
+ }
+
+ // If there's any remaining screen area, zero it out
+ byte *endP = (byte *)_surface->getPixels() + _surface->w * _surface->h;
+ if (destP != endP)
+ Common::fill(destP, endP, 0);
+}
+
+void RL2Decoder::RL2VideoTrack::rl2DecodeFrameWithTransparency(int screenOffset) {
+ int frameSize = _surface->w * _surface->h - screenOffset;
+ byte *refP = (byte *)_backSurface->getPixels();
+ byte *destP = (byte *)_surface->getPixels();
+
+ // If there's a screen offset, copy unchanged initial pixels from reference surface
+ if (screenOffset > 0)
+ Common::copy(refP, refP + screenOffset, destP);
+
+ // Main decode loop
+ while (frameSize > 0) {
+ byte nextByte = _fileStream->readByte();
+
+ if (nextByte == 0) {
+ // Move one single byte from reference surface
+ assert(frameSize > 0);
+ destP[screenOffset] = refP[screenOffset];
+ ++screenOffset;
+ --frameSize;
+ } else if (nextByte < 0x80) {
+ // Single 7-bit pixel to output (128-255)
+ assert(frameSize > 0);
+ destP[screenOffset] = nextByte | 0x80;
+ ++screenOffset;
+ --frameSize;
+ } else if (nextByte == 0x80) {
+ int runLength = _fileStream->readByte();
+ if (runLength == 0)
+ return;
+
+ // Run length of transparency (i.e. pixels to copy from reference frame)
+ runLength = MIN(runLength, frameSize);
+
+ Common::copy(refP + screenOffset, refP + screenOffset + runLength, destP + screenOffset);
+ screenOffset += runLength;
+ frameSize -= runLength;
+ } else {
+ // Run length of a single pixel value
+ int runLength = _fileStream->readByte();
+ runLength = MIN(runLength, frameSize);
+
+ Common::fill(destP + screenOffset, destP + screenOffset + runLength, nextByte);
+ screenOffset += runLength;
+ frameSize -= runLength;
+ }
+ }
+
+ // If there's a remaining section of the screen not covered, copy it from reference surface
+ if (screenOffset < (_surface->w * _surface->h))
+ Common::copy(refP + screenOffset, refP + (_surface->w * _surface->h), destP + screenOffset);
+}
+
+Graphics::Surface *RL2Decoder::RL2VideoTrack::getBackSurface() {
+ if (!_backSurface)
+ initBackSurface();
+
+ return _backSurface;
+}
+
+/*------------------------------------------------------------------------*/
+
+RL2Decoder::RL2AudioTrack::RL2AudioTrack(const RL2FileHeader &header, Common::SeekableReadStream *stream, Audio::Mixer::SoundType soundType):
+ _header(header), _soundType(soundType) {
+ // Create audio straem for the audio track
+ _audStream = Audio::makeQueuingAudioStream(_header._rate, _header._channels == 2);
+}
+
+RL2Decoder::RL2AudioTrack::~RL2AudioTrack() {
+ delete _audStream;
+}
+
+void RL2Decoder::RL2AudioTrack::queueSound(Common::SeekableReadStream *stream, int size) {
+ // Queue the sound data
+ byte *data = (byte *)malloc(size);
+ stream->read(data, size);
+ Common::MemoryReadStream *memoryStream = new Common::MemoryReadStream(data, size,
+ DisposeAfterUse::YES);
+
+ _audStream->queueAudioStream(Audio::makeRawStream(memoryStream, _header._rate,
+ Audio::FLAG_UNSIGNED, DisposeAfterUse::YES), DisposeAfterUse::YES);
+}
+
+Audio::AudioStream *RL2Decoder::RL2AudioTrack::getAudioStream() const {
+ return _audStream;
+}
+
+void RL2Decoder::play(VoyeurEngine *vm, int resourceOffset,
+ byte *frames, byte *imgPos) {
+ vm->flipPageAndWait();
+ int paletteStart = getPaletteStart();
+ int paletteCount = getPaletteCount();
+
+ PictureResource videoFrame(getVideoTrack()->getBackSurface());
+ int picCtr = 0;
+ while (!vm->shouldQuit() && !endOfVideo() && !vm->_eventsManager._mouseClicked) {
+ if (hasDirtyPalette()) {
+ const byte *palette = getPalette();
+
+ vm->_graphicsManager.setPalette128(palette, paletteStart, paletteCount);
+ }
+
+ if (needsUpdate()) {
+ if (frames) {
+ // If reached a point where a new background is needed, load it
+ // and copy over to the video decoder
+ if (getCurFrame() >= READ_LE_UINT16(frames + picCtr * 4)) {
+ PictureResource *newPic = vm->_bVoy->boltEntry(0x302 + picCtr)._picResource;
+ Common::Point pt(READ_LE_UINT16(imgPos + 4 * picCtr) - 32,
+ READ_LE_UINT16(imgPos + 4 * picCtr + 2) - 20);
+
+ vm->_graphicsManager.sDrawPic(newPic, &videoFrame, pt);
+ ++picCtr;
+ }
+ }
+
+ // Decode the next frame and display
+ const Graphics::Surface *frame = decodeNextFrame();
+ Common::copy((const byte *)frame->getPixels(), (const byte *)frame->getPixels() + 320 * 200,
+ (byte *)vm->_graphicsManager._screenSurface.getPixels());
+ }
+
+ vm->_eventsManager.getMouseInfo();
+ g_system->delayMillis(10);
+ }
+}
+
+} // End of namespace Voyeur
diff --git a/engines/voyeur/animation.h b/engines/voyeur/animation.h
new file mode 100644
index 0000000000..35199f6b93
--- /dev/null
+++ b/engines/voyeur/animation.h
@@ -0,0 +1,196 @@
+/* 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 VOYEUR_ANIMATION_H
+#define VOYEUR_ANIMATION_H
+
+#include "video/video_decoder.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "audio/timestamp.h"
+#include "common/array.h"
+#include "common/list.h"
+#include "common/rect.h"
+#include "common/stream.h"
+#include "voyeur/files.h"
+
+namespace Voyeur {
+
+class VoyeurEngine;
+
+/**
+ * Decoder for RL2 videos.
+ *
+ * Video decoder used in engines:
+ * - voyeur
+ */
+class RL2Decoder : public Video::VideoDecoder {
+private:
+ class RL2FileHeader {
+ public:
+ RL2FileHeader();
+ ~RL2FileHeader();
+
+ int _channels;
+ int _colorCount;
+ int _numFrames;
+ int _rate;
+ int _soundRate;
+ int _videoBase;
+ int *_frameSoundSizes;
+
+ uint32 _backSize;
+ uint32 _signature;
+ uint32 *_frameOffsets;
+
+ byte _palette[768];
+
+ void load(Common::SeekableReadStream *stream);
+ Common::Rational getFrameRate() const;
+ bool isValid() const;
+
+ private:
+ uint32 _form;
+ uint32 _dataSize;
+ int _method;
+ int _defSoundSize;
+ };
+
+ class SoundFrame {
+ public:
+ int _offset;
+ int _size;
+
+ SoundFrame(int offset, int size);
+ };
+
+ class RL2AudioTrack : public AudioTrack {
+ private:
+ Audio::Mixer::SoundType _soundType;
+ const RL2FileHeader &_header;
+ Audio::QueuingAudioStream *_audStream;
+ protected:
+ Audio::AudioStream *getAudioStream() const;
+ public:
+ RL2AudioTrack(const RL2FileHeader &header, Common::SeekableReadStream *stream,
+ Audio::Mixer::SoundType soundType);
+ ~RL2AudioTrack();
+
+ Audio::Mixer::SoundType getSoundType() const { return _soundType; }
+ int numQueuedStreams() const { return _audStream->numQueuedStreams(); }
+ virtual bool isSeekable() const { return true; }
+ virtual bool seek(const Audio::Timestamp &time) { return true; }
+
+ void queueSound(Common::SeekableReadStream *stream, int size);
+ };
+
+ class RL2VideoTrack : public FixedRateVideoTrack {
+ public:
+ RL2VideoTrack(const RL2FileHeader &header, RL2AudioTrack *audioTrack,
+ Common::SeekableReadStream *stream);
+ ~RL2VideoTrack();
+
+ uint16 getWidth() const;
+ uint16 getHeight() const;
+ Graphics::Surface *getSurface() { return _surface; }
+ Graphics::Surface *getBackSurface();
+ Graphics::PixelFormat getPixelFormat() const;
+ int getCurFrame() const { return _curFrame; }
+ int getFrameCount() const { return _header._numFrames; }
+ const Graphics::Surface *decodeNextFrame();
+ const byte *getPalette() const { _dirtyPalette = false; return _header._palette; }
+ int getPaletteCount() const { return _header._colorCount; }
+ bool hasDirtyPalette() const { return _dirtyPalette; }
+ const Common::List<Common::Rect> *getDirtyRects() const { return &_dirtyRects; }
+ void clearDirtyRects() { _dirtyRects.clear(); }
+ void copyDirtyRectsToBuffer(uint8 *dst, uint pitch);
+
+ virtual Common::Rational getFrameRate() const { return _header.getFrameRate(); }
+ virtual bool isSeekable() const { return true; }
+ virtual bool seek(const Audio::Timestamp &time);
+ private:
+ Common::SeekableReadStream *_fileStream;
+ const RL2FileHeader &_header;
+ RL2AudioTrack *_audioTrack;
+ Graphics::Surface *_surface;
+ Graphics::Surface *_backSurface;
+ bool _hasBackFrame;
+
+ mutable bool _dirtyPalette;
+
+ bool _initialFrame;
+ int _curFrame;
+ uint32 _videoBase;
+ uint32 *_frameOffsets;
+
+ Common::List<Common::Rect> _dirtyRects;
+
+ void copyFrame(uint8 *data);
+ void rl2DecodeFrameWithTransparency(int screenOffset);
+ void rl2DecodeFrameWithoutTransparency(int screenOffset = -1);
+ void initBackSurface();
+ };
+
+private:
+ RL2AudioTrack *_audioTrack;
+ RL2VideoTrack *_videoTrack;
+ Common::SeekableReadStream *_fileStream;
+ Audio::Mixer::SoundType _soundType;
+ RL2FileHeader _header;
+ int _paletteStart;
+ Common::Array<SoundFrame> _soundFrames;
+ int _soundFrameNumber;
+ const Common::List<Common::Rect> *getDirtyRects() const;
+
+ void clearDirtyRects();
+ void copyDirtyRectsToBuffer(uint8 *dst, uint pitch);
+ int getPaletteStart() const { return _paletteStart; }
+ const RL2FileHeader &getHeader() { return _header; }
+ virtual void readNextPacket();
+ virtual bool seekIntern(const Audio::Timestamp &time);
+
+public:
+ RL2Decoder(Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType);
+ virtual ~RL2Decoder();
+
+ virtual void close();
+
+ bool loadStream(Common::SeekableReadStream *stream);
+ bool loadFile(const Common::String &file, bool palFlag = false);
+ bool loadVideo(int videoId);
+ int getPaletteCount() const { return _header._colorCount; }
+
+ /**
+ * Play back a given Voyeur RL2 video
+ * @param vm Engine reference
+ * @param resourceOffset Starting resource to use for frame pictures
+ * @param frames Optional frame numbers resource for when to apply image data
+ * @param imgPos Position to draw image data
+ */
+ void play(VoyeurEngine *vm, int resourceOffset = 0, byte *frames = NULL, byte *imgPos = NULL);
+ RL2VideoTrack *getVideoTrack() { return _videoTrack; }
+ RL2AudioTrack *getAudioTrack() { return _audioTrack; }
+};
+
+} // End of namespace Voyeur
+
+#endif /* VOYEUR_ANIMATION_H */
diff --git a/engines/voyeur/configure.engine b/engines/voyeur/configure.engine
new file mode 100644
index 0000000000..23891fccb7
--- /dev/null
+++ b/engines/voyeur/configure.engine
@@ -0,0 +1,4 @@
+
+# This file is included from the main "configure" script
+# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
+add_engine voyeur "Voyeur" no
diff --git a/engines/voyeur/data.cpp b/engines/voyeur/data.cpp
new file mode 100644
index 0000000000..3a7c75fc2e
--- /dev/null
+++ b/engines/voyeur/data.cpp
@@ -0,0 +1,359 @@
+/* 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 "voyeur/data.h"
+#include "voyeur/voyeur.h"
+
+namespace Voyeur {
+
+void VoyeurEvent::synchronize(Common::Serializer &s) {
+ s.syncAsByte(_hour);
+ s.syncAsByte(_minute);
+ s.syncAsByte(_isAM);
+ s.syncAsByte(_type);
+ s.syncAsSint16LE(_audioVideoId);
+ s.syncAsSint16LE(_computerOn);
+ s.syncAsSint16LE(_computerOff);
+ s.syncAsSint16LE(_dead);
+}
+
+/*------------------------------------------------------------------------*/
+
+SVoy::SVoy() {
+ // Initialize all the data fields
+ _abortInterface = false;
+ _fadeICF0 = false;
+ _isAM = false;
+ Common::fill(&_phoneCallsReceived[0], &_phoneCallsReceived[5], false);
+ Common::fill(&_roomHotspotsEnabled[0], &_roomHotspotsEnabled[20], false);
+ _victimMurdered = false;
+
+ _audioVisualStartTime = 0;
+ _audioVisualDuration = 0;
+ _boltGroupId2 = 0;
+ _computerTextId = 0;
+ _computerTimeMin = _computerTimeMax = 0;
+ _curICF0 = 0;
+ _eventCount = 0;
+ _fadingStep1 = 0;
+ _fadingStep2 = 0;
+ _fadingType = 0;
+ _incriminatedVictimNumber = 0;
+ _musicStartTime = 0;
+ _playStampMode = 0;
+ _switchBGNum = 0;
+ _transitionId = 0;
+ _victimNumber = 0;
+ _videoEventId = 0;
+ _vocSecondsOffset = 0;
+ _RTANum = 0;
+ _RTVLimit = 0;
+ _RTVNum = 0;
+ _viewBounds = nullptr;
+ Common::fill(&_evPicPtrs[0], &_evPicPtrs[6], (PictureResource *)nullptr);
+ Common::fill(&_evCmPtrs[0], &_evCmPtrs[6], (CMapResource *)nullptr);
+ _curICF1 = 0;
+ _policeEvent = 0;
+
+ _eventFlags = EVTFLAG_TIME_DISABLED;
+ _fadingAmount1 = _fadingAmount2 = 127;
+ _murderThreshold = 9999;
+ _aptLoadMode = -1;
+ _eventFlags |= EVTFLAG_100;
+ _totalPhoneCalls = 0;
+}
+
+void SVoy::setVm(VoyeurEngine *vm) {
+ _vm = vm;
+}
+
+void SVoy::addEvent(int hour, int minute, VoyeurEventType type, int audioVideoId,
+ int on, int off, int dead) {
+ VoyeurEvent &e = _events[_eventCount++];
+
+ e._type = type;
+ e._hour = hour;
+ e._minute = minute;
+ e._isAM = hour < 12;
+ e._audioVideoId = audioVideoId;
+ e._computerOn = on;
+ e._computerOff = off;
+ e._dead = dead;
+}
+
+void SVoy::synchronize(Common::Serializer &s) {
+ s.syncAsByte(_isAM);
+ s.syncAsSint16LE(_RTANum);
+ s.syncAsSint16LE(_RTVNum);
+ s.syncAsSint16LE(_switchBGNum);
+
+ _videoHotspotTimes.synchronize(s);
+ _audioHotspotTimes.synchronize(s);
+ _evidenceHotspotTimes.synchronize(s);
+
+ for (int idx = 0; idx < 20; ++idx) {
+ s.syncAsByte(_roomHotspotsEnabled[idx]);
+ }
+
+ s.syncAsSint16LE(_audioVisualStartTime);
+ s.syncAsSint16LE(_audioVisualDuration);
+ s.syncAsSint16LE(_vocSecondsOffset);
+ s.syncAsSint16LE(_abortInterface);
+ s.syncAsSint16LE(_playStampMode);
+ s.syncAsSint16LE(_aptLoadMode);
+ s.syncAsSint16LE(_transitionId);
+ s.syncAsSint16LE(_RTVLimit);
+ s.syncAsSint16LE(_eventFlags);
+ s.syncAsSint16LE(_boltGroupId2);
+
+ s.syncAsSint16LE(_musicStartTime);
+ s.syncAsSint16LE(_totalPhoneCalls);
+ s.syncAsSint16LE(_computerTextId);
+ s.syncAsSint16LE(_computerTimeMin);
+ s.syncAsSint16LE(_computerTimeMax);
+ s.syncAsSint16LE(_victimMurdered);
+ s.syncAsSint16LE(_murderThreshold);
+
+ // Events
+ s.syncAsUint16LE(_eventCount);
+ for (int idx = 0; idx < _eventCount; ++idx)
+ _events[idx].synchronize(s);
+
+ s.syncAsSint16LE(_fadingAmount1);
+ s.syncAsSint16LE(_fadingAmount2);
+ s.syncAsSint16LE(_fadingStep1);
+ s.syncAsSint16LE(_fadingStep2);
+ s.syncAsSint16LE(_fadingType);
+ s.syncAsSint16LE(_victimNumber);
+ s.syncAsSint16LE(_incriminatedVictimNumber);
+ s.syncAsSint16LE(_videoEventId);
+
+ if (s.isLoading()) {
+ // Reset apartment loading mode to initial game value
+ _aptLoadMode = 140;
+ _viewBounds = nullptr;
+ }
+}
+
+void SVoy::addVideoEventStart() {
+ VoyeurEvent &e = _events[_eventCount];
+ e._hour = _vm->_gameHour;
+ e._minute = _vm->_gameMinute;
+ e._isAM = _isAM;
+ e._type = EVTYPE_VIDEO;
+ e._audioVideoId = _vm->_audioVideoId;
+ e._computerOn = _vocSecondsOffset;
+ e._dead = _vm->_eventsManager._videoDead;
+}
+
+void SVoy::addVideoEventEnd() {
+ VoyeurEvent &e = _events[_eventCount];
+ e._computerOff = _RTVNum - _audioVisualStartTime - _vocSecondsOffset;
+ if (_eventCount < (TOTAL_EVENTS - 1))
+ ++_eventCount;
+}
+
+void SVoy::addAudioEventStart() {
+ VoyeurEvent &e = _events[_eventCount];
+ e._hour = _vm->_gameHour;
+ e._minute = _vm->_gameMinute;
+ e._isAM = _isAM;
+ e._type = EVTYPE_AUDIO;
+ e._audioVideoId = _vm->_audioVideoId;
+ e._computerOn = _vocSecondsOffset;
+ e._dead = _vm->_eventsManager._videoDead;
+}
+
+void SVoy::addAudioEventEnd() {
+ VoyeurEvent &e = _events[_eventCount];
+ e._computerOff = _RTVNum - _audioVisualStartTime - _vocSecondsOffset;
+ if (_eventCount < (TOTAL_EVENTS - 1))
+ ++_eventCount;
+}
+
+void SVoy::addEvidEventStart(int v) {
+ VoyeurEvent &e = _events[_eventCount];
+ e._hour = _vm->_gameHour;
+ e._minute = _vm->_gameMinute;
+ e._isAM = _isAM;
+ e._type = EVTYPE_EVID;
+ e._audioVideoId = _vm->_playStampGroupId;
+ e._computerOn = _boltGroupId2;
+ e._computerOff = v;
+}
+
+void SVoy::addEvidEventEnd(int totalPages) {
+ VoyeurEvent &e = _events[_eventCount];
+ e._dead = totalPages;
+ if (_eventCount < (TOTAL_EVENTS - 1))
+ ++_eventCount;
+}
+
+void SVoy::addComputerEventStart() {
+ VoyeurEvent &e = _events[_eventCount];
+ e._hour = _vm->_gameHour;
+ e._minute = _vm->_gameMinute;
+ e._isAM = _isAM;
+ e._type = EVTYPE_COMPUTER;
+ e._audioVideoId = _vm->_playStampGroupId;
+ e._computerOn = _computerTextId;
+}
+
+void SVoy::addComputerEventEnd(int v) {
+ VoyeurEvent &e = _events[_eventCount];
+ e._computerOff = v;
+ if (_eventCount < (TOTAL_EVENTS - 1))
+ ++_eventCount;
+}
+
+void SVoy::reviewAnEvidEvent(int eventIndex) {
+ VoyeurEvent &e = _events[eventIndex];
+ _vm->_playStampGroupId = e._audioVideoId;
+ _boltGroupId2 = e._computerOn;
+ int frameOff = e._computerOff;
+
+ if (_vm->_bVoy->getBoltGroup(_vm->_playStampGroupId)) {
+ _vm->_graphicsManager._backColors = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + 1)._cMapResource;
+ _vm->_graphicsManager._backgroundPage = _vm->_bVoy->boltEntry(_vm->_playStampGroupId)._picResource;
+ (*_vm->_graphicsManager._vPort)->setupViewPort(_vm->_graphicsManager._backgroundPage);
+ _vm->_graphicsManager._backColors->startFade();
+
+ _vm->doEvidDisplay(frameOff, e._dead);
+ _vm->_bVoy->freeBoltGroup(_vm->_playStampGroupId);
+ _vm->_playStampGroupId = -1;
+
+ if (_boltGroupId2 != -1) {
+ _vm->_bVoy->freeBoltGroup(_boltGroupId2);
+ _boltGroupId2 = -1;
+ }
+ }
+}
+
+void SVoy::reviewComputerEvent(int eventIndex) {
+ VoyeurEvent &e = _events[eventIndex];
+ _vm->_playStampGroupId = e._audioVideoId;
+ _computerTextId = e._computerOn;
+
+ if (_vm->_bVoy->getBoltGroup(_vm->_playStampGroupId)) {
+ _vm->_graphicsManager._backColors = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + 1)._cMapResource;
+ _vm->_graphicsManager._backgroundPage = _vm->_bVoy->boltEntry(_vm->_playStampGroupId)._picResource;
+ (*_vm->_graphicsManager._vPort)->setupViewPort(_vm->_graphicsManager._backgroundPage);
+ _vm->_graphicsManager._backColors->startFade();
+ _vm->flipPageAndWaitForFade();
+
+ _vm->getComputerBrush();
+ _vm->flipPageAndWait();
+ _vm->doComputerText(e._computerOff);
+
+ _vm->_bVoy->freeBoltGroup(0x4900);
+ _vm->_bVoy->freeBoltGroup(_vm->_playStampGroupId);
+ _vm->_playStampGroupId = -1;
+ }
+}
+
+bool SVoy::checkForKey() {
+ _vm->_controlPtr->_state->_victimEvidenceIndex = 0;
+ if (_vm->_voy._victimMurdered)
+ return false;
+
+ for (int eventIdx = 0; eventIdx < _eventCount; ++eventIdx) {
+ VoyeurEvent &e = _events[eventIdx];
+
+ switch (e._type) {
+ case EVTYPE_VIDEO:
+ switch (_vm->_controlPtr->_state->_victimIndex) {
+ case 1:
+ if (e._audioVideoId == 33 && e._computerOn < 2 && e._computerOff >= 38)
+ _vm->_controlPtr->_state->_victimEvidenceIndex = 1;
+ break;
+
+ case 2:
+ if (e._audioVideoId == 47 && e._computerOn < 2 && e._computerOff >= 9)
+ _vm->_controlPtr->_state->_victimEvidenceIndex = 2;
+ break;
+
+ case 3:
+ if (e._audioVideoId == 46 && e._computerOn < 2 && e._computerOff > 2)
+ _vm->_controlPtr->_state->_victimEvidenceIndex = 3;
+ break;
+
+ case 4:
+ if (e._audioVideoId == 40 && e._computerOn < 2 && e._computerOff > 6)
+ _vm->_controlPtr->_state->_victimEvidenceIndex = 4;
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case EVTYPE_AUDIO:
+ switch (_vm->_controlPtr->_state->_victimIndex) {
+ case 1:
+ if (e._audioVideoId == 8 && e._computerOn < 2 && e._computerOff > 26)
+ _vm->_controlPtr->_state->_victimEvidenceIndex = 1;
+ break;
+
+ case 3:
+ if (e._audioVideoId == 20 && e._computerOn < 2 && e._computerOff > 28)
+ _vm->_controlPtr->_state->_victimEvidenceIndex = 3;
+ if (e._audioVideoId == 35 && e._computerOn < 2 && e._computerOff > 18)
+ _vm->_controlPtr->_state->_victimEvidenceIndex = 3;
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case EVTYPE_EVID:
+ switch (_vm->_controlPtr->_state->_victimIndex) {
+ case 4:
+ if (e._audioVideoId == 0x2400 && e._computerOn == 0x4f00 && e._computerOff == 17)
+ _vm->_controlPtr->_state->_victimEvidenceIndex = 4;
+
+ default:
+ break;
+ }
+ break;
+
+ case EVTYPE_COMPUTER:
+ switch (_vm->_controlPtr->_state->_victimIndex) {
+ case 2:
+ if (e._computerOn == 13 && e._computerOff > 76)
+ _vm->_controlPtr->_state->_victimEvidenceIndex = 2;
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+
+ if (_vm->_controlPtr->_state->_victimEvidenceIndex == _vm->_controlPtr->_state->_victimIndex)
+ return true;
+ }
+
+ return false;
+}
+
+} // End of namespace Voyeur
diff --git a/engines/voyeur/data.h b/engines/voyeur/data.h
new file mode 100644
index 0000000000..8eb11ff390
--- /dev/null
+++ b/engines/voyeur/data.h
@@ -0,0 +1,231 @@
+/* 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 VOYEUR_DATA_H
+#define VOYEUR_DATA_H
+
+#include "common/scummsys.h"
+#include "common/serializer.h"
+#include "voyeur/files.h"
+
+namespace Voyeur {
+
+#define TOTAL_EVENTS 1000
+
+enum VoyeurEventType { EVTYPE_VIDEO = 1, EVTYPE_AUDIO = 2, EVTYPE_EVID = 3,
+ EVTYPE_COMPUTER = 4 };
+
+enum EventFlag { EVTFLAG_TIME_DISABLED = 1, EVTFLAG_2 = 2, EVTFLAG_8 = 8, EVTFLAG_RECORDING = 0x10,
+ EVTFLAG_40 = 0x40, EVTFLAG_VICTIM_PRESET = 0x80, EVTFLAG_100 = 0x100 };
+
+struct VoyeurEvent {
+ int _hour;
+ int _minute;
+ bool _isAM;
+ VoyeurEventType _type;
+ int _audioVideoId;
+ int _computerOn;
+ int _computerOff;
+ int _dead;
+
+ void synchronize(Common::Serializer &s);
+};
+
+class VoyeurEngne;
+
+/**
+ * Encapsulates a list of the time expired ranges that hotspots in the mansion
+ * view are enabled for in a given time period.
+ */
+template<int SLOTS>
+class HotspotTimes {
+public:
+ int _min[SLOTS][20]; // Min time expired
+ int _max[SLOTS][20]; // Max time expired
+
+ HotspotTimes() {
+ reset();
+ }
+
+ /**
+ * Resets the data to an initial state
+ */
+ void reset() {
+ for (int hotspotIdx = 0; hotspotIdx < 20; ++hotspotIdx) {
+ for (int slotIdx = 0; slotIdx < SLOTS; ++slotIdx) {
+ _min[slotIdx][hotspotIdx] = 9999;
+ _max[slotIdx][hotspotIdx] = 0;
+ }
+ }
+ }
+
+ /**
+ * Synchronise the data
+ */
+ void synchronize(Common::Serializer &s) {
+ for (int slotIndex = 0; slotIndex < SLOTS; ++slotIndex) {
+ for (int hotspotIndex = 0; hotspotIndex < 20; ++hotspotIndex) {
+ s.syncAsSint16LE(_min[slotIndex][hotspotIndex]);
+ s.syncAsSint16LE(_max[slotIndex][hotspotIndex]);
+ }
+ }
+ }
+
+ /**
+ * Returns true if the given value is in the range specified by the
+ * min and max at the given hotspot and slot indexes
+ */
+ bool isInRange(int slotIndex, int hotspotIndex, int v) const {
+ return _min[slotIndex][hotspotIndex] <= v &&
+ v < _max[slotIndex][hotspotIndex];
+ }
+};
+
+class SVoy {
+private:
+ VoyeurEngine *_vm;
+
+public:
+ bool _abortInterface;
+ bool _fadeICF0; // Useless variable? (always the same value)
+ bool _isAM;
+ bool _phoneCallsReceived[5];
+ bool _roomHotspotsEnabled[20];
+ bool _victimMurdered;
+
+ int _aptLoadMode;
+ int _audioVisualStartTime;
+ int _audioVisualDuration;
+ int _boltGroupId2;
+ int _computerTextId;
+ int _computerTimeMin;
+ int _computerTimeMax;
+ int _curICF0; // Useless variable
+ int _eventCount;
+ int _eventFlags;
+ int _fadingAmount1;
+ int _fadingAmount2;
+ int _fadingStep1;
+ int _fadingStep2;
+ int _fadingType;
+ int _incriminatedVictimNumber;
+ int _murderThreshold;
+ int _musicStartTime;
+ int _playStampMode;
+ int _switchBGNum;
+ int _totalPhoneCalls;
+ int _transitionId;
+ int _victimNumber;
+ int _videoEventId;
+ int _vocSecondsOffset;
+ int _RTANum;
+ int _RTVLimit;
+ int _RTVNum;
+
+ HotspotTimes<3> _audioHotspotTimes;
+ HotspotTimes<3> _evidenceHotspotTimes;
+ HotspotTimes<8> _videoHotspotTimes;
+
+ Common::Rect _rect4E4;
+ RectResource *_viewBounds;
+ PictureResource *_evPicPtrs[6];
+ CMapResource *_evCmPtrs[6];
+ VoyeurEvent _events[TOTAL_EVENTS];
+
+ SVoy();
+ void setVm(VoyeurEngine *vm);
+
+ /**
+ * Synchronise the data
+ */
+ void synchronize(Common::Serializer &s);
+
+ /**
+ * Add an event to the list of game events that have occurred
+ */
+ void addEvent(int hour, int minute, VoyeurEventType type, int audioVideoId,
+ int on, int off, int dead);
+
+ /**
+ * Adds the start of a video event happening
+ */
+ void addVideoEventStart();
+
+ /**
+ * Adds the finish of a video event happening
+ */
+ void addVideoEventEnd();
+
+ /**
+ * Adds the start of an audio event happening
+ */
+ void addAudioEventStart();
+
+ /**
+ * Adsd the finish of an audio event happening
+ */
+ void addAudioEventEnd();
+
+ /**
+ * Adds the start of an evidence event happening
+ */
+ void addEvidEventStart(int v);
+
+ /**
+ * Adds the finish of an evidence event happening
+ */
+ void addEvidEventEnd(int totalPages);
+
+ /**
+ * Adds the start of a computer event happening
+ */
+ void addComputerEventStart();
+
+ /**
+ * Adds the finish of a computer event happening
+ */
+ void addComputerEventEnd(int v);
+
+ /**
+ * Review a previously recorded evidence event
+ */
+ void reviewAnEvidEvent(int eventIndex);
+
+ /**
+ * Review a previously recorded computer event
+ */
+ void reviewComputerEvent(int eventIndex);
+
+ /**
+ * Checks for key information in determining what kind of murder
+ * should take place
+ */
+ bool checkForKey();
+
+private:
+ int _curICF1; // Useless variable
+ int _policeEvent;
+};
+
+} // End of namespace Voyeur
+
+#endif /* VOYEUR_DATA_H */
diff --git a/engines/voyeur/debugger.cpp b/engines/voyeur/debugger.cpp
new file mode 100644
index 0000000000..b1bc2633ee
--- /dev/null
+++ b/engines/voyeur/debugger.cpp
@@ -0,0 +1,148 @@
+/* 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 "voyeur/debugger.h"
+#include "voyeur/graphics.h"
+#include "voyeur/voyeur.h"
+#include "voyeur/staticres.h"
+
+namespace Voyeur {
+
+Debugger::Debugger() : GUI::Debugger() {
+ // Register methods
+ DCmd_Register("continue", WRAP_METHOD(Debugger, Cmd_Exit));
+ DCmd_Register("exit", WRAP_METHOD(Debugger, Cmd_Exit));
+ DCmd_Register("time", WRAP_METHOD(Debugger, Cmd_Time));
+ DCmd_Register("hotspots", WRAP_METHOD(Debugger, Cmd_Hotspots));
+ DCmd_Register("mouse", WRAP_METHOD(Debugger, Cmd_Mouse));
+
+ // Set fields
+ _isTimeActive = true;
+ _showMousePosition = false;
+}
+
+static const int TIME_STATES[] = {
+ 0, 31, 0, 43, 59, 0, 67, 75, 85, 93, 0, 0, 111, 121, 0, 0
+};
+
+bool Debugger::Cmd_Time(int argc, const char **argv) {
+ if (argc < 2) {
+ // Get the current day and time of day
+ Common::String dtString = _vm->getDayName();
+ Common::String timeString = _vm->getTimeOfDay();
+ if (!timeString.empty())
+ dtString += " " + timeString;
+
+ DebugPrintf("Time period = %d, date/time is: %s, time is %s\n",
+ _vm->_voy._transitionId, dtString.c_str(), _isTimeActive ? "on" : "off");
+ DebugPrintf("Format: %s [on | off | 1..17 | val <amount>]\n\n", argv[0]);
+ } else {
+ if (!strcmp(argv[1], "on")) {
+ _isTimeActive = true;
+ DebugPrintf("Time is now on\n\n");
+ } else if (!strcmp(argv[1], "off")) {
+ _isTimeActive = false;
+ DebugPrintf("Time is now off\n\n");
+ } else if (!strcmp(argv[1], "val")) {
+ if (argc < 3) {
+ DebugPrintf("Time expired is currently %d.\n", _vm->_voy._RTVNum);
+ } else {
+ _vm->_voy._RTVNum = atoi(argv[2]);
+ DebugPrintf("Time expired is now %d.\n", _vm->_voy._RTVNum);
+ }
+ } else {
+ int timeId = atoi(argv[1]);
+ if (timeId >= 1 && timeId <= 17) {
+ int stateId = TIME_STATES[timeId - 1];
+ if (!stateId) {
+ DebugPrintf("Given time period is not used in-game\n");
+ } else {
+ DebugPrintf("Changing to time period: %d\n", timeId);
+ if (_vm->_mainThread->goToState(-1, stateId))
+ _vm->_mainThread->parsePlayCommands();
+
+ return false;
+ }
+ } else {
+ DebugPrintf("Unknown parameter\n\n");
+ }
+ }
+ }
+
+ return true;
+}
+
+bool Debugger::Cmd_Hotspots(int argc, const char **argv) {
+ BoltEntry &boltEntry = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + 1);
+ if (!boltEntry._rectResource) {
+ DebugPrintf("No hotspots available\n");
+ } else {
+ Common::Array<RectEntry> &hotspots = boltEntry._rectResource->_entries;
+
+ for (uint hotspotIdx = 0; hotspotIdx < hotspots.size(); ++hotspotIdx) {
+ Common::String pos = Common::String::format("(%d,%d->%d,%d)",
+ hotspots[hotspotIdx].left, hotspots[hotspotIdx].top,
+ hotspots[hotspotIdx].right, hotspots[hotspotIdx].bottom);
+
+ for (int arrIndex = 0; arrIndex < 3; ++arrIndex) {
+ if (_vm->_voy._audioHotspotTimes._min[arrIndex][hotspotIdx] != 9999) {
+ DebugPrintf("Hotspot %d %s Audio slot %d, time: %d to %d\n",
+ hotspotIdx, pos.c_str(), arrIndex,
+ _vm->_voy._audioHotspotTimes._min[arrIndex][hotspotIdx],
+ _vm->_voy._audioHotspotTimes._max[arrIndex][hotspotIdx]);
+ }
+
+ if (_vm->_voy._evidenceHotspotTimes._min[arrIndex][hotspotIdx] != 9999) {
+ DebugPrintf("Hotspot %d %s Evidence slot %d, time: %d to %d\n",
+ hotspotIdx, pos.c_str(), arrIndex,
+ _vm->_voy._evidenceHotspotTimes._min[arrIndex][hotspotIdx],
+ _vm->_voy._evidenceHotspotTimes._max[arrIndex][hotspotIdx]);
+ }
+ }
+
+ for (int arrIndex = 0; arrIndex < 8; ++arrIndex) {
+ if (_vm->_voy._videoHotspotTimes._min[arrIndex][hotspotIdx] != 9999) {
+ DebugPrintf("Hotspot %d %s Video slot %d, time: %d to %d\n",
+ hotspotIdx, pos.c_str(), arrIndex,
+ _vm->_voy._videoHotspotTimes._min[arrIndex][hotspotIdx],
+ _vm->_voy._videoHotspotTimes._max[arrIndex][hotspotIdx]);
+ }
+ }
+ }
+ }
+
+ DebugPrintf("\n");
+ return true;
+}
+
+bool Debugger::Cmd_Mouse(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("mouse [ on | off ]\n");
+ } else {
+ _showMousePosition = !strcmp(argv[1], "on");
+ DebugPrintf("Mouse position is now %s\n", _showMousePosition ? "on" : "off");
+ }
+
+ return true;
+}
+
+} // End of namespace Voyeur
diff --git a/engines/voyeur/debugger.h b/engines/voyeur/debugger.h
new file mode 100644
index 0000000000..43eaa5f474
--- /dev/null
+++ b/engines/voyeur/debugger.h
@@ -0,0 +1,72 @@
+/* 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 VOYEUR_DEBUGGER_H
+#define VOYEUR_DEBUGGER_H
+
+#include "common/scummsys.h"
+#include "gui/debugger.h"
+
+namespace Voyeur {
+
+class VoyeurEngine;
+
+class Debugger : public GUI::Debugger {
+private:
+ VoyeurEngine *_vm;
+public:
+ /**
+ * Specifies whether time should pass, and the video camera's batteries go down
+ * @default true
+ */
+ bool _isTimeActive;
+
+ /*
+ * Specifies whether to show the current mouse position on the screen
+ */
+ bool _showMousePosition;
+protected:
+ /**
+ * Turn time on or off, set the current time period, or the camera delay
+ * within the current time period.
+ */
+ bool Cmd_Time(int argc, const char **argv);
+
+ /**
+ * List the active hotspots during the current time period
+ */
+ bool Cmd_Hotspots(int argc, const char **argv);
+
+ /**
+ * Toggle showing the mouse on the screen
+ */
+ bool Cmd_Mouse(int argc, const char **argv);
+public:
+ Debugger();
+ virtual ~Debugger() {}
+ void setVm(VoyeurEngine *vm) { _vm = vm; }
+
+};
+
+} // End of namespace Voyeur
+
+#endif
diff --git a/engines/voyeur/detection.cpp b/engines/voyeur/detection.cpp
new file mode 100644
index 0000000000..ab3932bea1
--- /dev/null
+++ b/engines/voyeur/detection.cpp
@@ -0,0 +1,182 @@
+/* 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 "voyeur/voyeur.h"
+
+#include "base/plugins.h"
+#include "common/savefile.h"
+#include "common/str-array.h"
+#include "common/memstream.h"
+#include "engines/advancedDetector.h"
+#include "common/system.h"
+#include "graphics/colormasks.h"
+#include "graphics/surface.h"
+
+#define MAX_SAVES 99
+
+namespace Voyeur {
+
+struct VoyeurGameDescription {
+ ADGameDescription desc;
+};
+
+uint32 VoyeurEngine::getFeatures() const {
+ return _gameDescription->desc.flags;
+}
+
+Common::Language VoyeurEngine::getLanguage() const {
+ return _gameDescription->desc.language;
+}
+
+Common::Platform VoyeurEngine::getPlatform() const {
+ return _gameDescription->desc.platform;
+}
+
+bool VoyeurEngine::getIsDemo() const {
+ return _gameDescription->desc.flags & ADGF_DEMO;
+}
+
+} // End of namespace Voyeur
+
+static const PlainGameDescriptor voyeurGames[] = {
+ {"voyeur", "Voyeur"},
+ {0, 0}
+};
+
+#include "voyeur/detection_tables.h"
+
+class VoyeurMetaEngine : public AdvancedMetaEngine {
+public:
+ VoyeurMetaEngine() : AdvancedMetaEngine(Voyeur::gameDescriptions, sizeof(Voyeur::VoyeurGameDescription), voyeurGames) {
+ _maxScanDepth = 3;
+ }
+
+ virtual const char *getName() const {
+ return "Voyeur Engine";
+ }
+
+ virtual const char *getOriginalCopyright() const {
+ return "Voyeur (c) Philips P.O.V. Entertainment Group";
+ }
+
+ virtual bool hasFeature(MetaEngineFeature f) const;
+ virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const;
+ virtual SaveStateList listSaves(const char *target) const;
+ virtual int getMaximumSaveSlot() const;
+ virtual void removeSaveState(const char *target, int slot) const;
+ SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const;
+};
+
+bool VoyeurMetaEngine::hasFeature(MetaEngineFeature f) const {
+ return
+ (f == kSupportsListSaves) ||
+ (f == kSupportsLoadingDuringStartup) ||
+ (f == kSupportsDeleteSave) ||
+ (f == kSavesSupportMetaInfo) ||
+ (f == kSavesSupportThumbnail);
+}
+
+bool Voyeur::VoyeurEngine::hasFeature(EngineFeature f) const {
+ return
+ (f == kSupportsRTL) ||
+ (f == kSupportsLoadingDuringRuntime) ||
+ (f == kSupportsSavingDuringRuntime);
+}
+
+bool VoyeurMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
+ const Voyeur::VoyeurGameDescription *gd = (const Voyeur::VoyeurGameDescription *)desc;
+ if (gd) {
+ *engine = new Voyeur::VoyeurEngine(syst, gd);
+ }
+ return gd != 0;
+}
+
+SaveStateList VoyeurMetaEngine::listSaves(const char *target) const {
+ Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
+ Common::StringArray filenames;
+ Common::String saveDesc;
+ Common::String pattern = Common::String::format("%s.0??", target);
+
+ filenames = saveFileMan->listSavefiles(pattern);
+ sort(filenames.begin(), filenames.end()); // Sort to get the files in numerical order
+
+ SaveStateList saveList;
+ Voyeur::VoyeurSavegameHeader header;
+
+ for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
+ const char *ext = strrchr(file->c_str(), '.');
+ int slot = ext ? atoi(ext + 1) : -1;
+
+ if (slot >= 0 && slot <= MAX_SAVES) {
+ Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(*file);
+
+ if (in) {
+ if (header.read(in)) {
+ saveList.push_back(SaveStateDescriptor(slot, header._saveName));
+ header._thumbnail->free();
+ }
+ delete in;
+ }
+ }
+ }
+
+ return saveList;
+}
+
+int VoyeurMetaEngine::getMaximumSaveSlot() const {
+ return MAX_SAVES;
+}
+
+void VoyeurMetaEngine::removeSaveState(const char *target, int slot) const {
+ Common::String filename = Common::String::format("%s.%03d", target, slot);
+ g_system->getSavefileManager()->removeSavefile(filename);
+}
+
+SaveStateDescriptor VoyeurMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+ Common::String filename = Common::String::format("%s.%03d", target, slot);
+ Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(filename);
+
+ if (f) {
+ Voyeur::VoyeurSavegameHeader header;
+ header.read(f);
+ delete f;
+
+ // Create the return descriptor
+ SaveStateDescriptor desc(slot, header._saveName);
+ desc.setThumbnail(header._thumbnail);
+ desc.setSaveDate(header._saveYear, header._saveMonth, header._saveDay);
+ desc.setSaveTime(header._saveHour, header._saveMinutes);
+ desc.setPlayTime(header._totalFrames * GAME_FRAME_TIME);
+
+ return desc;
+ }
+
+ return SaveStateDescriptor();
+}
+
+
+#if PLUGIN_ENABLED_DYNAMIC(VOYEUR)
+ REGISTER_PLUGIN_DYNAMIC(VOYEUR, PLUGIN_TYPE_ENGINE, VoyeurMetaEngine);
+#else
+ REGISTER_PLUGIN_STATIC(VOYEUR, PLUGIN_TYPE_ENGINE, VoyeurMetaEngine);
+#endif
diff --git a/engines/voyeur/detection_tables.h b/engines/voyeur/detection_tables.h
new file mode 100644
index 0000000000..af0faf6acb
--- /dev/null
+++ b/engines/voyeur/detection_tables.h
@@ -0,0 +1,42 @@
+/* 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.
+ *
+ */
+
+namespace Voyeur {
+
+static const VoyeurGameDescription gameDescriptions[] = {
+ {
+ // Voyeur DOS English
+ {
+ "voyeur",
+ 0,
+ AD_ENTRY1s("a1100100.rl2", "b44630677618d034970ca0a13c1c1237", 336361),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_NO_FLAGS,
+ GUIO1(GUIO_NONE)
+ },
+ },
+
+ { AD_TABLE_END_MARKER }
+};
+
+} // End of namespace Voyeur
diff --git a/engines/voyeur/events.cpp b/engines/voyeur/events.cpp
new file mode 100644
index 0000000000..3b2369c1e1
--- /dev/null
+++ b/engines/voyeur/events.cpp
@@ -0,0 +1,624 @@
+/* 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 "voyeur/events.h"
+#include "voyeur/voyeur.h"
+#include "voyeur/staticres.h"
+#include "common/events.h"
+#include "graphics/cursorman.h"
+#include "graphics/font.h"
+#include "graphics/fontman.h"
+#include "graphics/palette.h"
+
+namespace Voyeur {
+
+IntNode::IntNode() {
+ _intFunc = NULL;
+ _curTime = 0;
+ _timeReset = 0;
+ _flags = 0;
+}
+
+IntNode::IntNode(uint16 curTime, uint16 timeReset, uint16 flags) {
+ _intFunc = NULL;
+ _curTime = curTime;
+ _timeReset = timeReset;
+ _flags = flags;
+}
+
+/*------------------------------------------------------------------------*/
+
+IntData::IntData() {
+ _flipWait = false;
+ _hasPalette = false;
+ _flashTimer = 0;
+ _flashStep = 0;
+ field26 = 0;
+ _skipFading = false;
+ _palStartIndex = 0;
+ _palEndIndex = 0;
+ _palette = NULL;
+}
+
+/*------------------------------------------------------------------------*/
+
+EventsManager::EventsManager(): _intPtr(_gameData),
+ _fadeIntNode(0, 0, 3), _cycleIntNode(0, 0, 3) {
+ _cycleStatus = 0;
+ _mouseButton = 0;
+ _fadeStatus = 0;
+ _priorFrameTime = g_system->getMillis();
+ _gameCounter = 0;
+ _counterFlag = false;
+ _recordBlinkCounter = 0;
+ _cursorBlinked = false;
+
+ Common::fill(&_cycleTime[0], &_cycleTime[4], 0);
+ Common::fill(&_cycleNext[0], &_cycleNext[4], (byte *)nullptr);
+ _cyclePtr = NULL;
+
+ _leftClick = _rightClick = false;
+ _mouseClicked = _mouseUnk = false;
+ _newLeftClick = _newRightClick = false;;
+ _newMouseClicked = _newMouseUnk = false;
+
+ _videoDead = 0;
+}
+
+void EventsManager::resetMouse() {
+ // No implementation
+}
+
+void EventsManager::startMainClockInt() {
+ _mainIntNode._intFunc = &EventsManager::mainVoyeurIntFunc;
+ _mainIntNode._flags = 0;
+ _mainIntNode._curTime = 0;
+ _mainIntNode._timeReset = _vm->_graphicsManager._palFlag ? 50 : 60;
+}
+
+void EventsManager::mainVoyeurIntFunc() {
+ if (!(_vm->_voy._eventFlags & EVTFLAG_TIME_DISABLED)) {
+ ++_vm->_voy._switchBGNum;
+
+ if (_vm->_debugger._isTimeActive) {
+ // Increase camera discharge
+ ++_vm->_voy._RTVNum;
+
+ // If the murder threshold has been set, and is passed, then flag the victim
+ // as murdered, which prevents sending the tape from succeeding
+ if (_vm->_voy._RTVNum >= _vm->_voy._murderThreshold)
+ _vm->_voy._victimMurdered = true;
+ }
+ }
+}
+
+void EventsManager::sWaitFlip() {
+ Common::Array<ViewPortResource *> &viewPorts = _vm->_graphicsManager._viewPortListPtr->_entries;
+ for (uint idx = 0; idx < viewPorts.size(); ++idx) {
+ ViewPortResource &viewPort = *viewPorts[idx];
+
+ if (_vm->_graphicsManager._saveBack && (viewPort._flags & DISPFLAG_40)) {
+ Common::Rect *clipPtr = _vm->_graphicsManager._clipPtr;
+ _vm->_graphicsManager._clipPtr = &viewPort._clipRect;
+
+ if (viewPort._restoreFn)
+ (_vm->_graphicsManager.*viewPort._restoreFn)(&viewPort);
+
+ _vm->_graphicsManager._clipPtr = clipPtr;
+ viewPort._rectListCount[viewPort._pageIndex] = 0;
+ viewPort._rectListPtr[viewPort._pageIndex]->clear();
+ viewPort._flags &= ~DISPFLAG_40;
+ }
+ }
+
+ while (_gameData._flipWait && !_vm->shouldQuit()) {
+ pollEvents();
+ g_system->delayMillis(10);
+ }
+}
+
+void EventsManager::checkForNextFrameCounter() {
+ // Check for next game frame
+ uint32 milli = g_system->getMillis();
+ if ((milli - _priorFrameTime) >= GAME_FRAME_TIME) {
+ _counterFlag = !_counterFlag;
+ if (_counterFlag)
+ ++_gameCounter;
+ _priorFrameTime = milli;
+
+ // Run the timer-based updates
+ voyeurTimer();
+
+ if ((_gameCounter % GAME_FRAME_RATE) == 0)
+ mainVoyeurIntFunc();
+
+ // Give time to the debugger
+ _vm->_debugger.onFrame();
+
+ // If mouse position display is on, display the position
+ if (_vm->_debugger._showMousePosition)
+ showMousePosition();
+
+ // Display the frame
+ g_system->copyRectToScreen((byte *)_vm->_graphicsManager._screenSurface.getPixels(),
+ SCREEN_WIDTH, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+ g_system->updateScreen();
+
+ // Signal the ScummVM debugger
+ _vm->_debugger.onFrame();
+ }
+}
+
+void EventsManager::showMousePosition() {
+ const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont));
+ Common::String mousePos = Common::String::format("(%d,%d)", _mousePos.x, _mousePos.y);
+ if (_vm->_voyeurArea == AREA_INTERFACE) {
+ Common::Point pt = _mousePos + _vm->_mansionViewPos - Common::Point(40, 27);
+ if (pt.x < 0) pt.x = 0;
+ if (pt.y < 0) pt.y = 0;
+
+ mousePos += Common::String::format(" - (%d,%d)", pt.x, pt.y);
+ }
+
+ _vm->_graphicsManager._screenSurface.fillRect(
+ Common::Rect(0, 0, 110, font.getFontHeight()), 0);
+ font.drawString(&_vm->_graphicsManager._screenSurface, mousePos,
+ 0, 0, 110, 63);
+}
+
+void EventsManager::voyeurTimer() {
+ _gameData._flashTimer += _gameData._flashStep;
+
+ if (--_gameData.field26 <= 0) {
+ if (_gameData._flipWait) {
+ _gameData._flipWait = false;
+ _gameData._skipFading = false;
+ }
+
+ _gameData.field26 >>= 8;
+ }
+
+ videoTimer();
+
+ // Iterate through the list of registered nodes
+ Common::List<IntNode *>::iterator i;
+ for (i = _intNodes.begin(); i != _intNodes.end(); ++i) {
+ IntNode &node = **i;
+
+ if (node._flags & 1)
+ continue;
+ if (!(node._flags & 2)) {
+ if (--node._curTime != 0)
+ continue;
+
+ node._curTime = node._timeReset;
+ }
+
+ (this->*node._intFunc)();
+ }
+
+}
+
+void EventsManager::videoTimer() {
+ if (_gameData._hasPalette) {
+ _gameData._hasPalette = false;
+
+ g_system->getPaletteManager()->setPalette(_gameData._palette +
+ _gameData._palStartIndex * 3, _gameData._palStartIndex,
+ _gameData._palEndIndex - _gameData._palStartIndex + 1);
+ }
+}
+
+void EventsManager::delay(int cycles) {
+ uint32 totalMilli = cycles * 1000 / GAME_FRAME_RATE;
+ uint32 delayEnd = g_system->getMillis() + totalMilli;
+
+ while (!_vm->shouldQuit() && g_system->getMillis() < delayEnd) {
+ g_system->delayMillis(10);
+
+ pollEvents();
+ }
+}
+
+void EventsManager::delayClick(int cycles) {
+ uint32 totalMilli = cycles * 1000 / GAME_FRAME_RATE;
+ uint32 delayEnd = g_system->getMillis() + totalMilli;
+
+ do {
+ g_system->delayMillis(10);
+ getMouseInfo();
+ } while (!_vm->shouldQuit() && g_system->getMillis() < delayEnd
+ && !_vm->_eventsManager._mouseClicked);
+}
+
+void EventsManager::pollEvents() {
+ checkForNextFrameCounter();
+
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ // Handle keypress
+ switch (event.type) {
+ case Common::EVENT_QUIT:
+ case Common::EVENT_RTL:
+ case Common::EVENT_KEYUP:
+ return;
+
+ case Common::EVENT_KEYDOWN:
+ // Check for debugger
+ if (event.kbd.keycode == Common::KEYCODE_d && (event.kbd.flags & Common::KBD_CTRL)) {
+ // Attach to the debugger
+ _vm->_debugger.attach();
+ _vm->_debugger.onFrame();
+ }
+ return;
+ case Common::EVENT_LBUTTONDOWN:
+ _mouseButton = 1;
+ _vm->_eventsManager._newLeftClick = true;
+ _vm->_eventsManager._newMouseClicked = true;
+ return;
+ case Common::EVENT_RBUTTONDOWN:
+ _mouseButton = 2;
+ _vm->_eventsManager._newRightClick = true;
+ _vm->_eventsManager._newMouseClicked = true;
+ return;
+ case Common::EVENT_LBUTTONUP:
+ case Common::EVENT_RBUTTONUP:
+ _vm->_eventsManager._newMouseClicked = false;
+ _vm->_eventsManager._newLeftClick = false;
+ _vm->_eventsManager._newRightClick = false;
+ _mouseButton = 0;
+ return;
+ case Common::EVENT_MOUSEMOVE:
+ _mousePos = event.mouse;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void EventsManager::startFade(CMapResource *cMap) {
+ _fadeIntNode._flags |= 1;
+ if (_cycleStatus & 1)
+ _cycleIntNode._flags |= 1;
+
+ _fadeFirstCol = cMap->_start;
+ _fadeLastCol = cMap->_end;
+ _fadeCount = cMap->_steps + 1;
+
+ if (cMap->_steps > 0) {
+ _fadeStatus = cMap->_fadeStatus | 1;
+ byte *vgaP = &_vm->_graphicsManager._VGAColors[_fadeFirstCol * 3];
+ int mapIndex = 0;
+
+ for (int idx = _fadeFirstCol; idx <= _fadeLastCol; ++idx, vgaP += 3) {
+ ViewPortPalEntry &palEntry = _vm->_graphicsManager._viewPortListPtr->_palette[idx];
+ palEntry._rEntry = vgaP[0] << 8;
+ int rDiff = (cMap->_entries[mapIndex * 3] << 8) - palEntry._rEntry;
+ palEntry._rChange = rDiff / cMap->_steps;
+
+ palEntry._gEntry = vgaP[1] << 8;
+ int gDiff = (cMap->_entries[mapIndex * 3 + 1] << 8) - palEntry._gEntry;
+ palEntry._gChange = gDiff / cMap->_steps;
+
+ palEntry._bEntry = vgaP[2] << 8;
+ int bDiff = (cMap->_entries[mapIndex * 3 + 2] << 8) - palEntry._bEntry;
+ palEntry._bChange = bDiff / cMap->_steps;
+
+ palEntry._palIndex = idx;
+ if (!(cMap->_fadeStatus & 1))
+ ++mapIndex;
+ }
+
+ if (cMap->_fadeStatus & 2)
+ _intPtr._skipFading = true;
+ _fadeIntNode._flags &= ~1;
+ } else {
+ byte *vgaP = &_vm->_graphicsManager._VGAColors[_fadeFirstCol * 3];
+ int mapIndex = 0;
+
+ for (int idx = _fadeFirstCol; idx <= _fadeLastCol; ++idx, vgaP += 3) {
+ Common::copy(&cMap->_entries[mapIndex], &cMap->_entries[mapIndex + 3], vgaP);
+
+ if (!(cMap->_fadeStatus & 1))
+ mapIndex += 3;
+ }
+
+ if (_intPtr._palStartIndex > _fadeFirstCol)
+ _intPtr._palStartIndex = _fadeFirstCol;
+ if (_intPtr._palEndIndex < _fadeLastCol)
+ _intPtr._palEndIndex = _fadeLastCol;
+
+ _intPtr._hasPalette = true;
+ }
+
+ if (_cycleStatus & 1)
+ _cycleIntNode._flags &= ~1;
+}
+
+void EventsManager::addIntNode(IntNode *node) {
+ _intNodes.push_back(node);
+}
+
+void EventsManager::addFadeInt() {
+ IntNode &node = _fade2IntNode;
+ node._intFunc = &EventsManager::fadeIntFunc;
+ node._flags = 0;
+ node._curTime = 0;
+ node._timeReset = 1;
+
+ addIntNode(&node);
+}
+
+void EventsManager::vDoFadeInt() {
+ if (_intPtr._skipFading)
+ return;
+ if (--_fadeCount == 0) {
+ _fadeIntNode._flags |= 1;
+ _fadeStatus &= ~1;
+ return;
+ }
+
+
+ for (int i = _fadeFirstCol; i <= _fadeLastCol; ++i) {
+ ViewPortPalEntry &palEntry = _vm->_graphicsManager._viewPortListPtr->_palette[i];
+ byte *vgaP = &_vm->_graphicsManager._VGAColors[palEntry._palIndex * 3];
+
+ palEntry._rEntry += palEntry._rChange;
+ palEntry._gEntry += palEntry._gChange;
+ palEntry._bEntry += palEntry._bChange;
+
+ vgaP[0] = palEntry._rEntry >> 8;
+ vgaP[1] = palEntry._gEntry >> 8;
+ vgaP[2] = palEntry._bEntry >> 8;
+ }
+
+ if (_intPtr._palStartIndex > _fadeFirstCol)
+ _intPtr._palStartIndex = _fadeFirstCol;
+ if (_intPtr._palEndIndex < _fadeLastCol)
+ _intPtr._palEndIndex = _fadeLastCol;
+
+ _intPtr._hasPalette = true;
+}
+
+void EventsManager::vDoCycleInt() {
+ for (int idx = 3; idx >= 0; --idx) {
+ if (_cyclePtr->_type[idx] && --_cycleTime[idx] <= 0) {
+ byte *pSrc = _cycleNext[idx];
+ byte *pPal = _vm->_graphicsManager._VGAColors;
+
+ if (_cyclePtr->_type[idx] != 1) {
+ // New palette data being specified - loop to set entries
+ do {
+ int palIndex = READ_LE_UINT16(pSrc);
+ pPal[palIndex * 3] = pSrc[3];
+ pPal[palIndex * 3 + 1] = pSrc[4];
+ pPal[palIndex * 3 + 1] = pSrc[5];
+ pSrc += 6;
+
+ if ((int16)READ_LE_UINT16(pSrc) >= 0) {
+ // Resetting back to start of cycle data
+ pSrc = _cycleNext[idx];
+ break;
+ }
+ } while (*(pSrc + 2) == 0);
+
+ _cycleNext[idx] = pSrc;
+ _cycleTime[idx] = pSrc[2];
+ } else {
+ // Palette rotation to be done
+ _cycleTime[idx] = pSrc[4];
+
+ if (pSrc[5] == 1) {
+ // Move palette entry to end of range
+ int start = READ_LE_UINT16(pSrc);
+ int end = READ_LE_UINT16(&pSrc[2]);
+ assert(start < 0x100 && end < 0x100);
+
+ // Store the RGB of the first entry to be moved
+ byte r = pPal[start * 3];
+ byte g = pPal[start * 3 + 1];
+ byte b = pPal[start * 3 + 2];
+
+ Common::copy(&pPal[start * 3 + 3], &pPal[end * 3 + 3], &pPal[start * 3]);
+
+ // Place the original saved entry at the end of the range
+ pPal[end * 3] = r;
+ pPal[end * 3 + 1] = g;
+ pPal[end * 3 + 2] = b;
+
+ if (_fadeStatus & 1) {
+ //dx = start, di = end
+ warning("TODO: Adjustment of ViewPortListResource");
+ }
+ } else {
+ // Move palette entry to start of range
+ int start = READ_LE_UINT16(pSrc);
+ int end = READ_LE_UINT16(&pSrc[2]);
+ assert(start < 0x100 && end < 0x100);
+
+ // Store the RGB of the entry to be moved
+ byte r = pPal[end * 3];
+ byte g = pPal[end * 3 + 1];
+ byte b = pPal[end * 3 + 2];
+
+ // Move the remainder of the range forwards one entry
+ Common::copy_backward(&pPal[start * 3], &pPal[end * 3], &pPal[end * 3 + 3]);
+
+ // Place the original saved entry at the end of the range
+ pPal[start * 3] = r;
+ pPal[start * 3 + 1] = g;
+ pPal[start * 3 + 2] = b;
+
+ if (_fadeStatus & 1) {
+ //dx = start, di = end
+ warning("TODO: Adjustment of ViewPortListResource");
+ }
+ }
+ }
+
+ _intPtr._hasPalette = true;
+ }
+ }
+}
+
+
+void EventsManager::fadeIntFunc() {
+ switch (_vm->_voy._fadingType) {
+ case 1:
+ if (_vm->_voy._fadingAmount1 < 63)
+ _vm->_voy._fadingAmount1 += _vm->_voy._fadingStep1;
+ if (_vm->_voy._fadingAmount2 < 63)
+ _vm->_voy._fadingAmount2 += _vm->_voy._fadingStep2;
+ if (_vm->_voy._fadingAmount1 > 63)
+ _vm->_voy._fadingAmount1 = 63;
+ if (_vm->_voy._fadingAmount2 > 63)
+ _vm->_voy._fadingAmount2 = 63;
+ if ((_vm->_voy._fadingAmount1 == 63) && (_vm->_voy._fadingAmount2 == 63))
+ _vm->_voy._fadingType = 0;
+ break;
+ case 2:
+ if (_vm->_voy._fadingAmount1 > 0)
+ _vm->_voy._fadingAmount1 -= _vm->_voy._fadingStep1;
+ if (_vm->_voy._fadingAmount2 > 0)
+ _vm->_voy._fadingAmount2 -= _vm->_voy._fadingStep2;
+ if (_vm->_voy._fadingAmount1 < 0)
+ _vm->_voy._fadingAmount1 = 0;
+ if (_vm->_voy._fadingAmount2 < 0)
+ _vm->_voy._fadingAmount2 = 0;
+ if ((_vm->_voy._fadingAmount1 == 0) && (_vm->_voy._fadingAmount2 == 0))
+ _vm->_voy._fadingType = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+void EventsManager::deleteIntNode(IntNode *node) {
+ _intNodes.remove(node);
+}
+
+void EventsManager::vInitColor() {
+ _fadeIntNode._intFunc = &EventsManager::vDoFadeInt;
+ _cycleIntNode._intFunc = &EventsManager::vDoCycleInt;
+
+ addIntNode(&_fadeIntNode);
+ addIntNode(&_cycleIntNode);
+}
+
+void EventsManager::setCursor(PictureResource *pic) {
+ PictureResource cursor;
+ cursor._bounds = pic->_bounds;
+ cursor._flags = DISPFLAG_CURSOR;
+
+ _vm->_graphicsManager.sDrawPic(pic, &cursor, Common::Point());
+}
+
+void EventsManager::setCursor(byte *cursorData, int width, int height) {
+ CursorMan.replaceCursor(cursorData, width, height, width / 2, height / 2, 0);
+}
+
+void EventsManager::setCursorColor(int idx, int mode) {
+ switch (mode) {
+ case 0:
+ _vm->_graphicsManager.setColor(idx, 90, 90, 232);
+ break;
+ case 1:
+ _vm->_graphicsManager.setColor(idx, 232, 90, 90);
+ break;
+ case 2:
+ _vm->_graphicsManager.setColor(idx, 90, 232, 90);
+ break;
+ case 3:
+ _vm->_graphicsManager.setColor(idx, 90, 232, 232);
+ break;
+ default:
+ break;
+ }
+}
+
+void EventsManager::showCursor() {
+ CursorMan.showMouse(true);
+}
+
+void EventsManager::hideCursor() {
+ CursorMan.showMouse(false);
+}
+
+void EventsManager::getMouseInfo() {
+ pollEvents();
+
+ if (_vm->_voy._eventFlags & EVTFLAG_RECORDING) {
+ if ((_gameCounter - _recordBlinkCounter) > 8) {
+ _recordBlinkCounter = _gameCounter;
+
+ if (_cursorBlinked) {
+ _cursorBlinked = false;
+ _vm->_graphicsManager.setOneColor(128, 220, 20, 20);
+ _vm->_graphicsManager.setColor(128, 220, 20, 20);
+ } else {
+ _cursorBlinked = true;
+ _vm->_graphicsManager.setOneColor(128, 220, 220, 220);
+ _vm->_graphicsManager.setColor(128, 220, 220, 220);
+ }
+ }
+ }
+
+ _vm->_eventsManager._mouseClicked = _vm->_eventsManager._newMouseClicked;
+ _vm->_eventsManager._leftClick = _vm->_eventsManager._newLeftClick;
+ _vm->_eventsManager._rightClick = _vm->_eventsManager._newRightClick;
+ _vm->_eventsManager._mouseUnk = _vm->_eventsManager._newMouseUnk;
+
+ _vm->_eventsManager._newMouseClicked = false;
+ _vm->_eventsManager._newLeftClick = false;
+ _vm->_eventsManager._newRightClick = false;
+ _vm->_eventsManager._mouseUnk = false;
+}
+
+void EventsManager::startCursorBlink() {
+ if (_vm->_voy._eventFlags & EVTFLAG_RECORDING) {
+ _vm->_graphicsManager.setOneColor(128, 55, 5, 5);
+ _vm->_graphicsManager.setColor(128, 220, 20, 20);
+ _intPtr._hasPalette = true;
+
+ _vm->_graphicsManager.drawDot();
+ //copySection();
+ }
+}
+
+void EventsManager::incrementTime(int amt) {
+ for (int i = 0; i < amt; ++i)
+ mainVoyeurIntFunc();
+}
+
+void EventsManager::stopEvidDim() {
+ deleteIntNode(&_evIntNode);
+}
+
+Common::String EventsManager::getEvidString(int eventIndex) {
+ assert(eventIndex <= _vm->_voy._eventCount);
+ VoyeurEvent &e = _vm->_voy._events[eventIndex];
+ return Common::String::format("%03d %.2d:%.2d %s %s", eventIndex + 1,
+ e._hour, e._minute, e._isAM ? AM : PM, EVENT_TYPE_STRINGS[e._type - 1]);
+}
+
+} // End of namespace Voyeur
diff --git a/engines/voyeur/events.h b/engines/voyeur/events.h
new file mode 100644
index 0000000000..eb6cf6d77f
--- /dev/null
+++ b/engines/voyeur/events.h
@@ -0,0 +1,153 @@
+/* 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 VOYEUR_EVENTS_H
+#define VOYEUR_EVENTS_H
+
+#include "common/scummsys.h"
+#include "common/list.h"
+#include "graphics/surface.h"
+#include "voyeur/files.h"
+
+namespace Voyeur {
+
+class VoyeurEngine;
+class EventsManager;
+class CMapResource;
+
+#define GAME_FRAME_RATE 50
+#define GAME_FRAME_TIME (1000 / GAME_FRAME_RATE)
+
+typedef void (EventsManager::*EventMethodPtr)();
+
+class IntNode {
+public:
+ EventMethodPtr _intFunc;
+ uint16 _curTime;
+ uint16 _timeReset;
+ uint32 _flags;
+public:
+ IntNode();
+ IntNode(uint16 curTime, uint16 timeReset, uint16 flags);
+};
+
+class IntData {
+public:
+ bool _flipWait;
+ int _flashTimer;
+ int _flashStep;
+ int field26;
+ bool _hasPalette;
+ bool _skipFading;
+ int _palStartIndex;
+ int _palEndIndex;
+ byte *_palette;
+public:
+ IntData();
+};
+
+class EventsManager {
+private:
+ VoyeurEngine *_vm;
+ uint32 _priorFrameTime;
+ bool _counterFlag;
+ uint32 _gameCounter;
+ uint32 _recordBlinkCounter; // Original field was called _joe :)
+ int _mouseButton;
+ Common::List<IntNode *> _intNodes;
+ Common::Point _mousePos;
+ bool _cursorBlinked;
+
+ void mainVoyeurIntFunc();
+private:
+ void checkForNextFrameCounter();
+ void voyeurTimer();
+ void videoTimer();
+ void vDoFadeInt();
+ void vDoCycleInt();
+ void fadeIntFunc();
+ void deleteIntNode(IntNode *node);
+
+ /**
+ * Debugger support method to show the mouse position
+ */
+ void showMousePosition();
+public:
+ IntData _gameData;
+ IntData &_intPtr;
+ IntNode _fadeIntNode;
+ IntNode _fade2IntNode;
+ IntNode _cycleIntNode;
+ IntNode _evIntNode;
+ IntNode _mainIntNode;
+ int _cycleStatus;
+ int _fadeFirstCol, _fadeLastCol;
+ int _fadeCount;
+ int _fadeStatus;
+
+ bool _leftClick, _rightClick;
+ bool _mouseClicked;
+ bool _mouseUnk;
+ bool _newMouseClicked;
+ bool _newLeftClick, _newRightClick;
+ bool _newMouseUnk;
+
+ int _videoDead;
+ int _cycleTime[4];
+ byte *_cycleNext[4];
+ VInitCycleResource *_cyclePtr;
+public:
+ EventsManager();
+ void setVm(VoyeurEngine *vm) { _vm = vm; }
+
+ void resetMouse();
+ void setMousePos(const Common::Point &p) { _mousePos = p; }
+ void startMainClockInt();
+ void sWaitFlip();
+ void vInitColor();
+
+ void delay(int cycles);
+ void delayClick(int cycles);
+ void pollEvents();
+ void startFade(CMapResource *cMap);
+ void addIntNode(IntNode *node);
+ void addFadeInt();
+
+ void setCursor(PictureResource *pic);
+ void setCursor(byte *cursorData, int width, int height);
+ void setCursorColor(int idx, int mode);
+ void showCursor();
+ void hideCursor();
+ Common::Point getMousePos() { return _mousePos; }
+ uint32 getGameCounter() const { return _gameCounter; }
+ void getMouseInfo();
+ void startCursorBlink();
+ void incrementTime(int amt);
+
+ void stopEvidDim();
+
+ Common::String getEvidString(int eventIndex);
+};
+
+} // End of namespace Voyeur
+
+#endif /* VOYEUR_EVENTS_H */
diff --git a/engines/voyeur/files.cpp b/engines/voyeur/files.cpp
new file mode 100644
index 0000000000..3f955fd066
--- /dev/null
+++ b/engines/voyeur/files.cpp
@@ -0,0 +1,1659 @@
+/* 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 "voyeur/files.h"
+#include "voyeur/graphics.h"
+#include "voyeur/voyeur.h"
+#include "voyeur/staticres.h"
+
+namespace Voyeur {
+
+#define BOLT_GROUP_SIZE 16
+
+BoltFilesState::BoltFilesState() {
+ _curLibPtr = NULL;
+ _curGroupPtr = NULL;
+ _curMemberPtr = NULL;
+ _curMemInfoPtr = NULL;
+ _fromGroupFlag = 0;
+ _xorMask = 0;
+ _encrypt = false;
+ _curFilePosition = 0;
+ _bufferEnd = 0;
+ _bufferBegin = 0;
+ _bytesLeft = 0;
+ _bufSize = 0;
+ _bufStart = NULL;
+ _bufPos = NULL;
+ _historyIndex = 0;
+ _runLength = 0;
+ _decompState = 0;
+ _runType = 0;
+ _runValue = 0;
+ _runOffset = 0;
+ Common::fill(&_historyBuffer[0], &_historyBuffer[0x200], 0);
+ _curFd = NULL;
+ _boltPageFrame = NULL;
+}
+
+#define NEXT_BYTE if (--_bytesLeft < 0) nextBlock()
+
+byte *BoltFilesState::decompress(byte *buf, int size, int mode) {
+ if (!buf) {
+ buf = new byte[size];
+ Common::fill(buf, buf + size, 0);
+ }
+ byte *bufP = buf;
+
+ if (mode & 8) {
+ _decompState = 1;
+ _runType = 0;
+ _runLength = size;
+ }
+
+ while (size > 0) {
+ if (!_decompState) {
+ NEXT_BYTE;
+ byte nextByte = *_bufPos++;
+
+ switch (nextByte & 0xC0) {
+ case 0:
+ _runType = 0;
+ _runLength = 30 - (nextByte & 0x1f) + 1;
+ break;
+ case 0x40:
+ _runType = 1;
+ _runLength = 35 - (nextByte & 0x1f);
+ NEXT_BYTE;
+ _runOffset = *_bufPos++ + ((nextByte & 0x20) << 3);
+ break;
+ case 0x80:
+ _runType = 1;
+ _runLength = (nextByte & 0x20) ? ((32 - (nextByte & 0x1f)) << 2) + 2 :
+ (32 - (nextByte & 0x1f)) << 2;
+ NEXT_BYTE;
+ _runOffset = *_bufPos++ << 1;
+ break;
+ default:
+ _runType = 2;
+
+ if (nextByte & 0x20) {
+ _runLength = 0;
+ } else {
+ NEXT_BYTE;
+ _runLength = ((32 - (nextByte & 0x1f)) + (*_bufPos++ << 5)) << 2;
+ NEXT_BYTE;
+ _bufPos++;
+ NEXT_BYTE;
+ _runValue = *_bufPos++;
+ }
+ break;
+ }
+
+ _runOffset = _historyIndex - _runOffset;
+ }
+
+ int runOffset = _runOffset & 0x1ff;
+ int len;
+ if (_runLength <= size) {
+ len = _runLength;
+ _decompState = 0;
+ } else {
+ _decompState = 1;
+ len = size;
+ _runLength -= size;
+ if (_runType == 1)
+ _runOffset += len;
+ }
+
+ // Reduce the remaining size
+ size -= len;
+
+ // Handle the run lengths
+ switch (_runType) {
+ case 0:
+ while (len-- > 0) {
+ NEXT_BYTE;
+ byte v = *_bufPos++;
+ _historyBuffer[_historyIndex] = v;
+ *bufP++ = v;
+ _historyIndex = (_historyIndex + 1) & 0x1ff;
+ }
+ break;
+ case 1:
+ while (len-- > 0) {
+ _historyBuffer[_historyIndex] = _historyBuffer[runOffset];
+ *bufP++ = _historyBuffer[runOffset];
+ _historyIndex = (_historyIndex + 1) & 0x1ff;
+ runOffset = (runOffset + 1) & 0x1ff;
+ }
+ break;
+ default:
+ while (len-- > 0) {
+ _historyBuffer[_historyIndex] = _runValue;
+ *bufP++ = _runValue;
+ _historyIndex = (_historyIndex + 1) & 0x1ff;
+ }
+ break;
+ }
+ }
+
+ return buf;
+}
+
+#undef NEXT_BYTE
+
+void BoltFilesState::nextBlock() {
+ if (&_curLibPtr->_file != _curFd || _curFd->pos() != _bufferEnd)
+ _curLibPtr->_file.seek(_bufferEnd);
+
+ _curFd = &_curLibPtr->_file;
+ _bufferBegin = _bufferEnd;
+ int bytesRead = _curFd->read(_bufStart, _bufSize);
+
+ _bufferEnd = _curFilePosition = _curFd->pos();
+ _bytesLeft = bytesRead - 1;
+ _bufPos = _bufStart;
+}
+
+/*------------------------------------------------------------------------*/
+
+FilesManager::FilesManager() {
+ _decompressSize = DECOMPRESS_SIZE;
+}
+
+bool FilesManager::openBoltLib(const Common::String &filename, BoltFile *&boltFile) {
+ if (boltFile != NULL) {
+ _boltFilesState._curLibPtr = boltFile;
+ return true;
+ }
+
+ // Create the bolt file interface object and load the index
+ if (filename == "bvoy.blt")
+ boltFile = _boltFilesState._curLibPtr = new BVoyBoltFile(_boltFilesState);
+ else if (filename == "stampblt.blt")
+ boltFile = _boltFilesState._curLibPtr = new StampBoltFile(_boltFilesState);
+ else
+ error("Unknown bolt file specified");
+
+ return true;
+}
+
+byte *FilesManager::fload(const Common::String &filename, int *size) {
+ Common::File f;
+ int filesize;
+ byte *data = NULL;
+
+ if (f.open(filename)) {
+ // Read in the file
+ filesize = f.size();
+ data = new byte[filesize];
+ f.read(data, filesize);
+ } else {
+ filesize = 0;
+ }
+
+ if (size)
+ *size = filesize;
+ return data;
+}
+
+/*------------------------------------------------------------------------*/
+
+BoltFile::BoltFile(const Common::String &filename, BoltFilesState &state): _state(state) {
+ if (!_file.open(filename))
+ error("Could not open %s", filename.c_str());
+ _state._curFilePosition = 0;
+
+ // Read in the file header
+ byte header[16];
+ _file.read(&header[0], 16);
+
+ if (strncmp((const char *)&header[0], "BOLT", 4) != 0)
+ error("Tried to load non-bolt file");
+
+ int totalGroups = header[11] ? header[11] : 0x100;
+ for (int i = 0; i < totalGroups; ++i)
+ _groups.push_back(BoltGroup(&_file));
+}
+
+BoltFile::~BoltFile() {
+ _file.close();
+ if (_state._curFd == &_file)
+ _state._curFd = NULL;
+ if (_state._curLibPtr == this)
+ _state._curLibPtr = NULL;
+}
+
+BoltGroup *BoltFile::getBoltGroup(uint16 id, bool process) {
+ ++_state._fromGroupFlag;
+ _state._curLibPtr = this;
+ _state._curGroupPtr = &_groups[(id >> 8) & 0xff];
+
+ if (!_state._curGroupPtr->_loaded) {
+ // Load the group index
+ _state._curGroupPtr->load(id & 0xff00);
+ }
+
+ if (_state._curGroupPtr->_callInitGro)
+ initGro();
+
+ if (process) {
+ // Pre-process the resources
+ id &= 0xff00;
+ for (int idx = 0; idx < _state._curGroupPtr->_count; ++idx, ++id) {
+ byte *member = getBoltMember(id);
+ assert(member);
+ }
+ } else if (!_state._curGroupPtr->_processed) {
+ _state._curGroupPtr->_processed = true;
+ _state._curGroupPtr->load(id & 0xff00);
+ }
+
+ resolveAll();
+ --_state._fromGroupFlag;
+
+ return _state._curGroupPtr;
+}
+
+void BoltFile::freeBoltGroup(uint16 id, bool freeEntries) {
+ _state._curLibPtr = this;
+ _state._curGroupPtr = &_groups[(id >> 8) & 0xff];
+
+ // Unload the group
+ _state._curGroupPtr->unload();
+}
+
+void BoltFile::freeBoltMember(uint32 id) {
+ // No implementation in ScummVM
+}
+
+BoltEntry &BoltFile::getBoltEntryFromLong(uint32 id) {
+ BoltGroup &group = _groups[id >> 24];
+ assert(group._loaded);
+
+ BoltEntry &entry = group._entries[(id >> 16) & 0xff];
+ assert(!entry.hasResource() || (id & 0xffff) == 0);
+
+ return entry;
+}
+
+BoltEntry &BoltFile::boltEntry(uint16 id) {
+ BoltGroup &group = _groups[id >> 8];
+ assert(group._loaded);
+
+ BoltEntry &entry = group._entries[id & 0xff];
+ assert(entry.hasResource());
+
+ return entry;
+}
+
+PictureResource *BoltFile::getPictureResource(uint32 id) {
+ if ((int32)id == -1)
+ return NULL;
+
+ if (id & 0xffff)
+ id <<= 16;
+ return getBoltEntryFromLong(id)._picResource;
+}
+
+CMapResource *BoltFile::getCMapResource(uint32 id) {
+ if ((int32)id == -1)
+ return NULL;
+
+ if (id & 0xffff)
+ id <<= 16;
+
+ return getBoltEntryFromLong(id)._cMapResource;
+}
+
+byte *BoltFile::memberAddr(uint32 id) {
+ BoltGroup &group = _groups[id >> 8];
+ if (!group._loaded)
+ return NULL;
+
+ // If an entry already has a processed representation, we shouldn't
+ // still be accessing the raw data
+ BoltEntry &entry = group._entries[id & 0xff];
+ assert(!entry.hasResource());
+
+ return entry._data;
+}
+
+byte *BoltFile::memberAddrOffset(uint32 id) {
+ BoltGroup &group = _groups[id >> 24];
+ if (!group._loaded)
+ return NULL;
+
+ // If an entry already has a processed representation, we shouldn't
+ // still be accessing the raw data
+ BoltEntry &entry = group._entries[(id >> 16) & 0xff];
+ assert(!entry.hasResource());
+
+ return entry._data + (id & 0xffff);
+}
+
+/**
+ * Resolves an Id to an offset within a loaded resource
+ */
+void BoltFile::resolveIt(uint32 id, byte **p) {
+ if ((int32)id == -1) {
+ *p = NULL;
+ } else {
+ byte *ptr = memberAddrOffset(id);
+ if (ptr) {
+ *p = ptr;
+ } else {
+ *p = NULL;
+ assert(_state._resolves.size() < 1000);
+ _state._resolves.push_back(ResolveEntry(id, p));
+ }
+ }
+}
+
+void BoltFile::resolveFunction(uint32 id, GraphicMethodPtr *fn) {
+ if ((int32)id == -1)
+ *fn = NULL;
+ else
+ error("Function fnTermGro array not supported");
+}
+
+/**
+ * Resolve any data references to within resources that weren't
+ * previously loaded, but are now
+ */
+void BoltFile::resolveAll() {
+ for (uint idx = 0; idx < _state._resolves.size(); ++idx)
+ *_state._resolves[idx]._p = memberAddrOffset(_state._resolves[idx]._id);
+
+ _state._resolves.clear();
+}
+
+byte *BoltFile::getBoltMember(uint32 id) {
+ _state._curLibPtr = this;
+
+ // Get the group, and load it's entry list if not already loaded
+ _state._curGroupPtr = &_groups[(id >> 8) & 0xff];
+ if (!_state._curGroupPtr->_loaded)
+ _state._curGroupPtr->load(id & 0xff00);
+
+ // Get the entry
+ _state._curMemberPtr = &_state._curGroupPtr->_entries[id & 0xff];
+ if (_state._curMemberPtr->_initMemRequired)
+ initMem(_state._curMemberPtr->_initMemRequired);
+
+ // Return the data for the entry if it's already been loaded
+ if (_state._curMemberPtr->_data)
+ return _state._curMemberPtr->_data;
+
+ _state._xorMask = _state._curMemberPtr->_xorMask;
+ _state._encrypt = (_state._curMemberPtr->_mode & 0x10) != 0;
+
+ if (_state._curGroupPtr->_processed) {
+ error("Processed resources are not supported");
+ } else {
+ _state._bufStart = _state._decompressBuf;
+ _state._bufSize = DECOMPRESS_SIZE;
+
+ if ((_state._curFd != &_file) || (_state._curMemberPtr->_fileOffset < _state._bufferBegin)
+ || (_state._curMemberPtr->_fileOffset >= _state._bufferEnd)) {
+ _state._bytesLeft = 0;
+ _state._bufPos = _state._bufStart;
+ _state._bufferBegin = -1;
+ _state._bufferEnd = _state._curMemberPtr->_fileOffset;
+ } else {
+ _state._bufPos = _state._curMemberPtr->_fileOffset - _state._bufferBegin + _state._bufStart;
+ _state._bytesLeft = _state._bufSize - (_state._bufPos - _state._bufStart);
+ }
+ }
+
+ _state._decompState = 0;
+ _state._historyIndex = 0;
+
+ // Initialize the resource
+ assert(_state._curMemberPtr->_initMethod < 25);
+ initResource(_state._curMemberPtr->_initMethod);
+
+ return _state._curMemberPtr->_data;
+}
+
+void BoltFile::initDefault() {
+ _state._curMemberPtr->_data = _state.decompress(0, _state._curMemberPtr->_size,
+ _state._curMemberPtr->_mode);
+}
+
+/*------------------------------------------------------------------------*/
+
+BVoyBoltFile::BVoyBoltFile(BoltFilesState &state): BoltFile("bvoy.blt", state) {
+}
+
+void BVoyBoltFile::initResource(int resType) {
+ switch (resType) {
+ case 2:
+ // Also used for point list, and ending credits credit data
+ sInitRect();
+ break;
+ case 8:
+ sInitPic();
+ break;
+ case 10:
+ vInitCMap();
+ break;
+ case 11:
+ vInitCycl();
+ break;
+ case 15:
+ initViewPort();
+ break;
+ case 16:
+ initViewPortList();
+ break;
+ case 17:
+ initFont();
+ break;
+ case 18:
+ initFontInfo();
+ break;
+ case 19:
+ initSoundMap();
+ break;
+ default:
+ initDefault();
+ break;
+ }
+}
+
+void BVoyBoltFile::initViewPort() {
+ initDefault();
+
+ ViewPortResource *viewPort;
+ byte *src = _state._curMemberPtr->_data;
+ _state._curMemberPtr->_viewPortResource = viewPort = new ViewPortResource(_state, src);
+
+ // This is done post-constructor, since viewports can be self referential, so
+ // we ned the _viewPortResource field to have been set before resolving the pointer
+ viewPort->_parent = getBoltEntryFromLong(READ_LE_UINT32(src + 2))._viewPortResource;
+}
+
+void BVoyBoltFile::initViewPortList() {
+ initDefault();
+
+ ViewPortListResource *res;
+ _state._curMemberPtr->_viewPortListResource = res = new ViewPortListResource(
+ _state, _state._curMemberPtr->_data);
+
+ _state._vm->_graphicsManager._viewPortListPtr = res;
+ _state._vm->_graphicsManager._vPort = &res->_entries[0];
+}
+
+void BVoyBoltFile::initFontInfo() {
+ initDefault();
+ _state._curMemberPtr->_fontInfoResource = new FontInfoResource(
+ _state, _state._curMemberPtr->_data);
+}
+
+void BVoyBoltFile::initFont() {
+ initDefault();
+ _state._curMemberPtr->_fontResource = new FontResource(_state, _state._curMemberPtr->_data);
+}
+
+void BVoyBoltFile::initSoundMap() {
+ initDefault();
+}
+
+void BVoyBoltFile::sInitRect() {
+ _state._curMemberPtr->_data = _state.decompress(NULL, _state._curMemberPtr->_size,
+ _state._curMemberPtr->_mode);
+
+ // Check whether the resouce Id is in the list of extended rects
+ bool isExtendedRects = false;
+ for (int i = 0; i < 49 && !isExtendedRects; ++i)
+ isExtendedRects = RESOLVE_TABLE[i] == (_state._curMemberPtr->_id & 0xff00);
+
+ int rectSize = isExtendedRects ? 12 : 8;
+ if ((_state._curMemberPtr->_size % rectSize) == 0 || (_state._curMemberPtr->_size % rectSize) == 2)
+ _state._curMemberPtr->_rectResource = new RectResource(_state._curMemberPtr->_data,
+ _state._curMemberPtr->_size, isExtendedRects);
+}
+
+void BVoyBoltFile::sInitPic() {
+ // Read in the header data
+ _state._curMemberPtr->_data = _state.decompress(NULL, 24, _state._curMemberPtr->_mode);
+ _state._curMemberPtr->_picResource = new PictureResource(_state,
+ _state._curMemberPtr->_data);
+}
+
+void BVoyBoltFile::vInitCMap() {
+ initDefault();
+ _state._curMemberPtr->_cMapResource = new CMapResource(
+ _state, _state._curMemberPtr->_data);
+}
+
+void BVoyBoltFile::vInitCycl() {
+ initDefault();
+ _state._curMemberPtr->_vInitCycleResource = new VInitCycleResource(
+ _state, _state._curMemberPtr->_data);
+ _state._curMemberPtr->_vInitCycleResource->vStopCycle();
+}
+
+/*------------------------------------------------------------------------*/
+
+StampBoltFile::StampBoltFile(BoltFilesState &state): BoltFile("stampblt.blt", state) {
+}
+
+void StampBoltFile::initResource(int resType) {
+ switch (resType) {
+ case 0:
+ initThread();
+ break;
+ case 4:
+ initState();
+ break;
+ case 6:
+ initPtr();
+ break;
+ case 24:
+ initControl();
+ break;
+ default:
+ initDefault();
+ break;
+ }
+}
+
+void StampBoltFile::initThread() {
+ initDefault();
+
+ _state._curMemberPtr->_threadResource = new ThreadResource(_state,
+ _state._curMemberPtr->_data);
+}
+
+void StampBoltFile::initPtr() {
+ initDefault();
+
+ _state._curMemberPtr->_ptrResource = new PtrResource(_state,
+ _state._curMemberPtr->_data);
+}
+
+ void initControlData();
+
+
+void StampBoltFile::initControl() {
+ initDefault();
+
+ ControlResource *res;
+ _state._curMemberPtr->_controlResource = res = new ControlResource(_state,
+ _state._curMemberPtr->_data);
+
+ _state._vm->_controlGroupPtr = _state._curGroupPtr;
+ _state._vm->_controlPtr = res;
+}
+
+void StampBoltFile::initState() {
+ initDefault();
+
+ assert(_state._curMemberPtr->_size == 16);
+ _state._curMemberPtr->_stateResource = new StateResource(_state,
+ _state._curMemberPtr->_data);
+}
+
+/*------------------------------------------------------------------------*/
+
+BoltGroup::BoltGroup(Common::SeekableReadStream *f): _file(f) {
+ byte buffer[BOLT_GROUP_SIZE];
+
+ _loaded = false;
+
+ _file->read(&buffer[0], BOLT_GROUP_SIZE);
+ _processed = buffer[0] != 0;
+ _callInitGro = buffer[1] != 0;
+ _termGroIndex = buffer[2];
+ _count = buffer[3] ? buffer[3] : 256;
+ _fileOffset = READ_LE_UINT32(&buffer[8]);
+}
+
+BoltGroup::~BoltGroup() {
+}
+
+void BoltGroup::load(uint16 groupId) {
+ _file->seek(_fileOffset);
+
+ // Read the entries
+ for (int i = 0; i < _count; ++i)
+ _entries.push_back(BoltEntry(_file, groupId + i));
+
+ _loaded = true;
+}
+
+void BoltGroup::unload() {
+ if (!_loaded)
+ return;
+
+ _entries.clear();
+ _loaded = false;
+}
+
+/*------------------------------------------------------------------------*/
+
+BoltEntry::BoltEntry(Common::SeekableReadStream *f, uint16 id): _file(f), _id(id) {
+ _data = nullptr;
+ _rectResource = nullptr;
+ _picResource = nullptr;
+ _viewPortResource = nullptr;
+ _viewPortListResource = nullptr;
+ _fontResource = nullptr;
+ _fontInfoResource = nullptr;
+ _cMapResource = nullptr;
+ _vInitCycleResource = nullptr;
+
+ _ptrResource = nullptr;
+ _stateResource = nullptr;
+ _controlResource = nullptr;
+ _vInitCycleResource = nullptr;
+ _threadResource = nullptr;
+
+ byte buffer[16];
+ _file->read(&buffer[0], 16);
+ _mode = buffer[0];
+ _initMemRequired = buffer[1];
+ _initMethod = buffer[3];
+ _xorMask = buffer[4] & 0xff;
+ _size = READ_LE_UINT32(&buffer[4]) & 0xffffff;
+ _fileOffset = READ_LE_UINT32(&buffer[8]);
+}
+
+BoltEntry::~BoltEntry() {
+ delete[] _data;
+ delete _rectResource;
+ delete _picResource;
+ delete _viewPortResource;
+ delete _viewPortListResource;
+ delete _fontResource;
+ delete _fontInfoResource;
+ delete _cMapResource;
+
+ delete _ptrResource;
+ delete _controlResource;
+ delete _stateResource;
+ delete _vInitCycleResource;
+ delete _threadResource;
+}
+
+void BoltEntry::load() {
+ // Currently, all entry loading and decompression is done in BoltFile::memberAddr.
+}
+
+/**
+ * Returns true if the given bolt entry has an attached resource
+ */
+bool BoltEntry::hasResource() const {
+ return _rectResource || _picResource || _viewPortResource || _viewPortListResource
+ || _fontResource || _fontInfoResource || _cMapResource
+ || _vInitCycleResource
+ || _ptrResource || _controlResource || _stateResource || _threadResource;
+}
+
+/*------------------------------------------------------------------------*/
+
+RectEntry::RectEntry(int x1, int y1, int x2, int y2, int arrIndex, int count):
+ Common::Rect(x1, y1, x2, y2), _arrIndex(arrIndex), _count(count) {
+}
+
+/*------------------------------------------------------------------------*/
+
+RectResource::RectResource(const byte *src, int size, bool isExtendedRects) {
+ int count;
+ int rectSize = isExtendedRects ? 12 : 8;
+ if ((size % rectSize) == 2) {
+ count = READ_LE_UINT16(src);
+ src += 2;
+ } else {
+ count = size / rectSize;
+ }
+
+ for (int i = 0; i < count; ++i, src += 8) {
+ int arrIndex = 0, count = 0;
+ if (isExtendedRects) {
+ arrIndex = READ_LE_UINT16(src);
+ count = READ_LE_UINT16(src + 2);
+ src += 4;
+ }
+
+ int x1 = READ_LE_UINT16(src);
+ int y1 = READ_LE_UINT16(src + 2);
+ int x2 = READ_LE_UINT16(src + 4);
+ int y2 = READ_LE_UINT16(src + 6);
+
+ _entries.push_back(RectEntry(x1, y1, x2, y2, arrIndex, count));
+ }
+
+ left = _entries[0].left;
+ top = _entries[0].top;
+ right = _entries[0].right;
+ bottom = _entries[0].bottom;
+}
+
+RectResource::RectResource(int x1, int y1, int x2, int y2) {
+ left = x1;
+ top = y1;
+ right = x2;
+ bottom = y2;
+}
+
+/*------------------------------------------------------------------------*/
+
+DisplayResource::DisplayResource() {
+ _vm = NULL;
+}
+
+DisplayResource::DisplayResource(VoyeurEngine *vm) {
+ _vm = vm;
+}
+
+void DisplayResource::sFillBox(int width, int height) {
+ assert(_vm);
+ bool saveBack = _vm->_graphicsManager._saveBack;
+ _vm->_graphicsManager._saveBack = false;
+
+ PictureResource pr;
+ pr._flags = 1;
+ pr._select = 0xff;
+ pr._pick = 0;
+ pr._onOff = _vm->_graphicsManager._drawPtr->_penColor;
+ pr._bounds = Common::Rect(0, 0, width, height);
+
+ _vm->_graphicsManager.sDrawPic(&pr, this, _vm->_graphicsManager._drawPtr->_pos);
+ _vm->_graphicsManager._saveBack = saveBack;
+}
+
+bool DisplayResource::clipRect(Common::Rect &rect) {
+ Common::Rect clipRect;
+ if (_vm->_graphicsManager._clipPtr) {
+ clipRect = *_vm->_graphicsManager._clipPtr;
+ } else if (_flags & DISPFLAG_VIEWPORT) {
+ clipRect = ((ViewPortResource *)this)->_clipRect;
+ } else {
+ clipRect = ((PictureResource *)this)->_bounds;
+ }
+
+ Common::Rect r = rect;
+ if (r.left < clipRect.left) {
+ if (r.right <= clipRect.left)
+ return false;
+ r.setWidth(r.right - clipRect.left);
+ }
+ if (r.right >= clipRect.right) {
+ if (r.left >= clipRect.left)
+ return false;
+ r.setWidth(clipRect.right - r.left);
+ }
+
+ if (r.top < clipRect.top) {
+ if (r.bottom <= clipRect.top)
+ return false;
+ r.setHeight(r.bottom - clipRect.top);
+ }
+ if (r.bottom >= clipRect.bottom) {
+ if (r.top >= clipRect.top)
+ return false;
+ r.setWidth(clipRect.bottom - r.top);
+ }
+
+ rect = r;
+ return true;
+}
+
+int DisplayResource::drawText(const Common::String &msg) {
+ GraphicsManager &gfxManager = _vm->_graphicsManager;
+ assert(gfxManager._fontPtr);
+ assert(gfxManager._fontPtr->_curFont);
+ FontInfoResource &fontInfo = *gfxManager._fontPtr;
+ PictureResource &fontChar = *_vm->_graphicsManager._fontChar;
+ FontResource &fontData = *fontInfo._curFont;
+ int xShadows[9] = { 0, 1, 1, 1, 0, -1, -1, -1, 0 };
+ int yShadows[9] = { 0, 1, 0, -1, -1, -1, 0, 1, 1 };
+
+ Common::Rect *clipPtr = gfxManager._clipPtr;
+ if (!(fontInfo._picFlags & DISPFLAG_1))
+ gfxManager._clipPtr = NULL;
+
+ int minChar = fontData._minChar;
+ int padding = fontData._padding;
+ int fontHeight = fontData._fontHeight;
+ int totalChars = fontData._maxChar - fontData._minChar;
+ int msgWidth = 0;
+ int xp = 0, yp = 0;
+
+ Common::Point pos = Common::Point(fontInfo._pos.x, fontInfo._pos.y + fontData._topPadding);
+
+ fontChar._flags = fontInfo._picFlags | DISPFLAG_2;
+ fontChar._select = fontInfo._picSelect;
+ fontChar._bounds.setHeight(fontHeight);
+
+ ViewPortResource *viewPort = !(_flags & DISPFLAG_VIEWPORT) ? NULL :
+ (ViewPortResource *)this;
+
+ if (gfxManager._drawTextPermFlag || (fontInfo._fontFlags & DISPFLAG_1) || fontInfo._justify ||
+ (gfxManager._saveBack && fontInfo._fontSaveBack && (_flags & DISPFLAG_VIEWPORT))) {
+ msgWidth = viewPort->textWidth(msg);
+ yp = pos.y;
+ xp = pos.x;
+
+ switch (fontInfo._justify) {
+ case 1:
+ xp = pos.x + (fontInfo._justifyWidth / 2) - (msgWidth / 2);
+ break;
+ case 2:
+ xp = pos.x + fontInfo._justifyWidth - msgWidth;
+ break;
+ default:
+ break;
+ }
+
+ if (!(fontInfo._fontFlags & 3)) {
+ viewPort->_fontRect.left = xp;
+ viewPort->_fontRect.top = yp;
+ viewPort->_fontRect.setWidth(msgWidth);
+ viewPort->_fontRect.setHeight(fontHeight);
+ } else {
+ viewPort->_fontRect.left = pos.x;
+ viewPort->_fontRect.top = pos.y;
+ viewPort->_fontRect.setWidth(fontInfo._justifyWidth);
+ viewPort->_fontRect.setHeight(fontInfo._justifyHeight);
+ }
+
+ pos.x = xp;
+ pos.y = yp;
+
+ if (fontInfo._fontFlags & 4) {
+ if (fontInfo._shadow.x <= 0) {
+ viewPort->_fontRect.left += fontInfo._shadow.x;
+ viewPort->_fontRect.right -= fontInfo._shadow.x * 2;
+ } else {
+ viewPort->_fontRect.right += fontInfo._shadow.x;
+ }
+
+ if (fontInfo._shadow.y <= 0) {
+ viewPort->_fontRect.top += fontInfo._shadow.y;
+ viewPort->_fontRect.bottom -= fontInfo._shadow.y * 2;
+ } else {
+ viewPort->_fontRect.bottom += fontInfo._shadow.y;
+ }
+ } else if (fontInfo._fontFlags & 8) {
+ if (fontInfo._shadow.x <= 0) {
+ viewPort->_fontRect.left += fontInfo._shadow.x;
+ viewPort->_fontRect.right -= fontInfo._shadow.x * 3;
+ } else {
+ viewPort->_fontRect.right += fontInfo._shadow.x * 3;
+ viewPort->_fontRect.left -= fontInfo._shadow.x;
+ }
+
+ if (fontInfo._shadow.y <= 0) {
+ viewPort->_fontRect.top += fontInfo._shadow.y;
+ viewPort->_fontRect.bottom -= fontInfo._shadow.y * 3;
+ } else {
+ viewPort->_fontRect.bottom += fontInfo._shadow.y * 3;
+ viewPort->_fontRect.top -= fontInfo._shadow.y;
+ }
+ }
+ }
+
+ if (gfxManager._saveBack && fontInfo._fontSaveBack && (_flags & DISPFLAG_VIEWPORT)) {
+ viewPort->addSaveRect(viewPort->_pageIndex, viewPort->_fontRect);
+ }
+
+ if (fontInfo._fontFlags & DISPFLAG_1) {
+ gfxManager._drawPtr->_pos = Common::Point(viewPort->_fontRect.left, viewPort->_fontRect.top);
+ gfxManager._drawPtr->_penColor = fontInfo._backColor;
+ sFillBox(viewPort->_fontRect.width(), viewPort->_fontRect.height());
+ }
+
+ bool saveBack = gfxManager._saveBack;
+ gfxManager._saveBack = false;
+
+ int count = 0;
+ if (fontInfo._fontFlags & DISPFLAG_4)
+ count = 1;
+ else if (fontInfo._fontFlags & DISPFLAG_8)
+ count = 8;
+
+ for (int i = count; i >= 0; --i) {
+ xp = pos.x;
+ yp = pos.y;
+
+ switch (xShadows[i]) {
+ case -1:
+ xp -= fontInfo._shadow.x;
+ break;
+ case 1:
+ xp += fontInfo._shadow.x;
+ break;
+ default:
+ break;
+ }
+
+ switch (yShadows[i]) {
+ case -1:
+ yp -= fontInfo._shadow.y;
+ break;
+ case 1:
+ yp += fontInfo._shadow.y;
+ break;
+ default:
+ break;
+ }
+
+ if (i != 0) {
+ fontChar._pick = 0;
+ fontChar._onOff = fontInfo._shadowColor;
+ } else if (fontData._fontDepth == 1 || (fontInfo._fontFlags & 0x10)) {
+ fontChar._pick = 0;
+ fontChar._onOff = fontInfo._foreColor;
+ } else {
+ fontChar._pick = fontInfo._picPick;
+ fontChar._onOff = fontInfo._picOnOff;
+ fontChar._depth = fontData._fontDepth;
+ }
+
+ // Loop to draw each character in turn
+ msgWidth = -padding;
+ const char *msgP = msg.c_str();
+ char ch;
+
+ while ((ch = *msgP++) != '\0') {
+ int charValue = (int)ch - minChar;
+ if (charValue < 0 || charValue >= totalChars || fontData._charWidth[charValue] == 0)
+ charValue = fontData._maxChar - minChar;
+
+ int charWidth = fontData._charWidth[charValue];
+ fontChar._bounds.setWidth(charWidth);
+
+ uint16 offset = READ_LE_UINT16(fontData._charOffsets + charValue * 2);
+ fontChar._imgData = fontData._charImages + offset * 2;
+
+ gfxManager.sDrawPic(&fontChar, this, Common::Point(xp, yp));
+
+ fontChar._imgData = NULL;
+ xp += charWidth + padding;
+ msgWidth += charWidth + padding;
+ }
+ }
+
+ msgWidth = MAX(msgWidth, 0);
+ if (fontInfo._justify == ALIGN_LEFT)
+ fontInfo._pos.x = xp;
+
+ gfxManager._saveBack = saveBack;
+ gfxManager._clipPtr = clipPtr;
+
+ return msgWidth;
+}
+
+int DisplayResource::textWidth(const Common::String &msg) {
+ if (msg.size() == 0)
+ return 0;
+
+ const char *msgP = msg.c_str();
+ FontResource &fontData = *_vm->_graphicsManager._fontPtr->_curFont;
+ int minChar = fontData._minChar;
+ int maxChar = fontData._maxChar;
+ int padding = fontData._padding;
+ int totalWidth = -padding;
+ char ch;
+
+ // Loop through the characters
+ while ((ch = *msgP++) != '\0') {
+ int charValue = (int)ch;
+ if (charValue < minChar || charValue > maxChar)
+ charValue = maxChar;
+
+ if (!fontData._charWidth[charValue - minChar])
+ charValue = maxChar;
+
+ totalWidth += fontData._charWidth[charValue - minChar] + padding;
+ }
+
+ if (totalWidth < 0)
+ totalWidth = 0;
+ return totalWidth;
+}
+
+/*------------------------------------------------------------------------*/
+
+PictureResource::PictureResource(BoltFilesState &state, const byte *src):
+ DisplayResource(state._vm) {
+ _flags = READ_LE_UINT16(src);
+ _select = src[2];
+ _pick = src[3];
+ _onOff = src[4];
+ _depth = src[5];
+
+ int xs = READ_LE_UINT16(&src[6]);
+ int ys = READ_LE_UINT16(&src[8]);
+ _bounds = Common::Rect(xs, ys, xs + READ_LE_UINT16(&src[10]),
+ ys + READ_LE_UINT16(&src[12]));
+ _maskData = READ_LE_UINT32(&src[14]);
+ _planeSize = READ_LE_UINT16(&src[22]);
+
+ _imgData = NULL;
+ _freeImgData = DisposeAfterUse::YES;
+
+ int nbytes = _bounds.width() * _bounds.height();
+ if (_flags & PICFLAG_20) {
+ if (_flags & (PICFLAG_VFLIP | PICFLAG_HFLIP)) {
+ // Get the raw data for the picture from another resource
+ uint32 id = READ_LE_UINT32(&src[18]);
+ const byte *srcData = state._curLibPtr->boltEntry(id)._data;
+ _imgData = new byte[nbytes];
+
+ // Flip the image data either horizontally or vertically
+ if (_flags & PICFLAG_HFLIP)
+ flipHorizontal(srcData);
+ else
+ flipVertical(srcData);
+ } else {
+ uint32 id = READ_LE_UINT32(&src[18]) >> 16;
+ byte *imgData = state._curLibPtr->boltEntry(id)._picResource->_imgData;
+ _freeImgData = DisposeAfterUse::NO;
+
+ // TODO: Double check code below. Despite different coding in the
+ // original, both looked like they do the same formula
+ if (_flags & PICFLAG_PIC_OFFSET) {
+ _imgData = imgData + (READ_LE_UINT32(&src[18]) & 0xffff);
+ } else {
+ _imgData = imgData + (READ_LE_UINT32(&src[18]) & 0xffff);
+ }
+ }
+ } else if (_flags & PICFLAG_PIC_OFFSET) {
+ int mode = 0;
+ if (_bounds.width() == 320) {
+ mode = 147;
+ state._sImageShift = 2;
+ state._SVGAReset = false;
+ } else {
+ state._SVGAReset = true;
+ if (_bounds.width() == 640) {
+ if (_bounds.height() == 400) {
+ mode = 220;
+ state._sImageShift = 3;
+ } else {
+ mode = 221;
+ state._sImageShift = 3;
+ }
+ } else if (_bounds.width() == 800) {
+ mode = 222;
+ state._sImageShift = 3;
+ } else if (_bounds.width() == 1024) {
+ mode = 226;
+ state._sImageShift = 3;
+ }
+ }
+
+ if (mode != state._vm->_graphicsManager._SVGAMode) {
+ state._vm->_graphicsManager._SVGAMode = mode;
+ state._vm->_graphicsManager.clearPalette();
+ }
+
+ int screenOffset = READ_LE_UINT32(&src[18]) & 0xffff;
+ assert(screenOffset == 0);
+
+ if (_flags & PICFLAG_CLEAR_SCREEN) {
+ // Clear screen picture. That's right. This game actually has a picture
+ // resource flag to clear the screen! Bizarre.
+ Graphics::Surface &s = state._vm->_graphicsManager._screenSurface;
+ s.fillRect(Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), 0);
+ } else {
+ // Direct sceren loading picture. In this case, the raw data of the resource
+ // is directly decompressed into the screen surface. Again, bizarre.
+ byte *pDest = (byte *)state._vm->_graphicsManager._screenSurface.getPixels();
+ state.decompress(pDest, SCREEN_WIDTH * SCREEN_HEIGHT, state._curMemberPtr->_mode);
+ }
+ } else {
+ if (_flags & PICFLAG_CLEAR_SCREEN00) {
+ if (!(_flags & PICFLAG_CLEAR_SCREEN))
+ nbytes = state._curMemberPtr->_size - 24;
+
+ int mask = (nbytes + 0x3FFF) >> 14;
+ _imgData = NULL;
+
+ if (state._boltPageFrame == 0)
+ state.EMSGetFrameAddr(&state._boltPageFrame);
+ if (state._boltPageFrame != 0) {
+ if (!state.EMSAllocatePages(&_planeSize)) {
+ _maskData = mask;
+
+ for (int idx = 0; idx < mask; ++idx)
+ state.EMSMapPageHandle(_planeSize, idx, idx);
+
+ state.decompress(state._boltPageFrame, nbytes, state._curMemberPtr->_mode);
+ return;
+ }
+ }
+ }
+
+ if (_flags & PICFLAG_CLEAR_SCREEN) {
+ _imgData = new byte[nbytes];
+ Common::fill(_imgData, _imgData + nbytes, 0);
+ } else {
+ _imgData = state.decompress(NULL, nbytes, state._curMemberPtr->_mode);
+ }
+ }
+}
+
+PictureResource::PictureResource(Graphics::Surface *surface) {
+ _flags = 0;
+ _select = 0;
+ _pick = 0;
+ _onOff = 0;
+ _depth = 0;
+ _maskData = 0;
+ _planeSize = 0;
+
+ _bounds = Common::Rect(0, 0, surface->w, surface->h);
+ _imgData = (byte *)surface->getPixels();
+ _freeImgData = DisposeAfterUse::NO;
+}
+
+PictureResource::PictureResource() {
+ _flags = 0;
+ _select = 0;
+ _pick = 0;
+ _onOff = 0;
+ _depth = 0;
+ _maskData = 0;
+ _planeSize = 0;
+
+ _imgData = NULL;
+ _freeImgData = DisposeAfterUse::NO;
+}
+
+PictureResource::PictureResource(int flags, int select, int pick, int onOff,
+ int depth, const Common::Rect &bounds, int maskData, byte *imgData,
+ int planeSize) {
+ _flags = flags;
+ _select = select;
+ _pick = pick;
+ _onOff = onOff;
+ _depth = depth;
+ _bounds = bounds;
+ _maskData = maskData;
+ _imgData = imgData;
+ _planeSize = planeSize;
+}
+
+PictureResource::~PictureResource() {
+ if (_freeImgData == DisposeAfterUse::YES)
+ delete[] _imgData;
+}
+
+void PictureResource::flipHorizontal(const byte *data) {
+ const byte *srcP = data + 18;
+ byte *destP = _imgData + _bounds.width() - 1;
+
+ for (int y = 0; y < _bounds.height(); ++y) {
+ for (int x = 0; x < _bounds.width(); ++x, ++srcP, --destP)
+ *destP = *srcP;
+
+ srcP += _bounds.width();
+ destP += _bounds.width();
+ }
+}
+
+void PictureResource::flipVertical(const byte *data) {
+ const byte *srcP = data + 18;
+ byte *destP = _imgData + _bounds.width() * (_bounds.height() - 1);
+
+ for (int y = 0; y < _bounds.height(); ++y) {
+ Common::copy(srcP, srcP + _bounds.width(), destP);
+ srcP += _bounds.width();
+ destP -= _bounds.width();
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+ViewPortResource::ViewPortResource(BoltFilesState &state, const byte *src):
+ _state(state), DisplayResource(state._vm) {
+ _flags = READ_LE_UINT16(src);
+ _parent = NULL;
+ _pageCount = READ_LE_UINT16(src + 6);
+ _pageIndex = READ_LE_UINT16(src + 8);
+ _lastPage = READ_LE_UINT16(src + 10);
+
+ int xs = READ_LE_UINT16(src + 12);
+ int ys = READ_LE_UINT16(src + 14);
+ _bounds = Common::Rect(xs, ys, xs + READ_LE_UINT16(src + 16),
+ ys + READ_LE_UINT16(src + 18));
+ _field18 = READ_LE_UINT16(src + 0x18);
+
+ _currentPic = state._curLibPtr->getPictureResource(READ_LE_UINT32(src + 0x20));
+ _activePage = state._curLibPtr->getPictureResource(READ_LE_UINT32(src + 0x24));
+ _pages[0] = state._curLibPtr->getPictureResource(READ_LE_UINT32(src + 0x28));
+ _pages[1] = state._curLibPtr->getPictureResource(READ_LE_UINT32(src + 0x2C));
+
+ state._curLibPtr->resolveIt(READ_LE_UINT32(src + 0x30), &_field30);
+
+ // Get the rect list
+ for (int listIndex = 0; listIndex < 3; ++listIndex) {
+ _rectListCount[listIndex] = (int16)READ_LE_UINT16(src + 0x40 + 2 * listIndex);
+ int id = (int)READ_LE_UINT32(src + 0x34 + listIndex * 4);
+
+ if (id == -1) {
+ _rectListPtr[listIndex] = NULL;
+ } else {
+ _rectListPtr[listIndex] = new Common::Array<Common::Rect>();
+
+ if (_rectListCount[listIndex] > 0) {
+ int16 *rectList = (int16 *)state._curLibPtr->memberAddrOffset(id);
+ for (int i = 0; i < _rectListCount[listIndex]; ++i) {
+ xs = FROM_LE_16(rectList[0]);
+ ys = FROM_LE_16(rectList[1]);
+ _rectListPtr[i]->push_back(Common::Rect(xs, ys, xs + FROM_LE_16(rectList[2]),
+ ys + FROM_LE_16(rectList[3])));
+ }
+ }
+ }
+ }
+
+ xs = READ_LE_UINT16(src + 0x46);
+ ys = READ_LE_UINT16(src + 0x48);
+ _clipRect = Common::Rect(xs, ys, xs + READ_LE_UINT16(src + 0x4A),
+ ys + READ_LE_UINT16(src + 0x4C));
+
+ state._curLibPtr->resolveIt(READ_LE_UINT32(src + 0x7A), &_field7A);
+
+ state._curLibPtr->resolveFunction(READ_LE_UINT32(src + 0x7E), (GraphicMethodPtr *)&_fn1);
+ state._curLibPtr->resolveFunction(READ_LE_UINT32(src + 0x82), (GraphicMethodPtr *)&_setupFn);
+ state._curLibPtr->resolveFunction(READ_LE_UINT32(src + 0x86), (GraphicMethodPtr *)&_addFn);
+ state._curLibPtr->resolveFunction(READ_LE_UINT32(src + 0x8A), (GraphicMethodPtr *)&_restoreFn);
+
+ if (!_restoreFn && _addFn)
+ _addFn = &GraphicsManager::addRectNoSaveBack;
+}
+
+ViewPortResource::~ViewPortResource() {
+ for (int i = 0; i < 3; ++i)
+ delete _rectListPtr[i];
+}
+
+void ViewPortResource::setupViewPort(PictureResource *page, Common::Rect *clipRect,
+ ViewPortSetupPtr setupFn, ViewPortAddPtr addFn, ViewPortRestorePtr restoreFn) {
+ PictureResource *pic = _currentPic;
+ Common::Rect r = _bounds;
+ r.translate(pic->_bounds.left, pic->_bounds.top);
+ int xDiff, yDiff;
+
+ if (page) {
+ // Clip based on the passed picture resource
+ xDiff = page->_bounds.left - r.left;
+ yDiff = page->_bounds.top - r.top;
+
+ if (xDiff > 0) {
+ int width = r.width();
+ r.left = page->_bounds.left;
+ r.setWidth(xDiff <= width ? width - xDiff : 0);
+ }
+ if (yDiff > 0) {
+ int height = r.height();
+ r.top = page->_bounds.top;
+ r.setHeight(yDiff <= height ? height - yDiff : 0);
+ }
+
+ xDiff = r.right - page->_bounds.right;
+ yDiff = r.bottom - page->_bounds.bottom;
+
+ if (xDiff > 0)
+ r.setWidth(xDiff <= r.width() ? r.width() - xDiff : 0);
+ if (yDiff > 0)
+ r.setHeight(yDiff <= r.height() ? r.height() - yDiff : 0);
+ }
+
+ if (clipRect) {
+ // Clip based on the passed clip rectangles
+ xDiff = clipRect->left - r.left;
+ yDiff = clipRect->top - r.top;
+
+ if (xDiff > 0) {
+ int width = r.width();
+ r.left = clipRect->left;
+ r.setWidth(xDiff <= width ? width - xDiff : 0);
+ }
+ if (yDiff > 0) {
+ int height = r.height();
+ r.top = clipRect->top;
+ r.setHeight(yDiff <= height ? height - yDiff : 0);
+ }
+
+ xDiff = r.right - clipRect->right;
+ yDiff = r.bottom - clipRect->bottom;
+
+ if (xDiff > 0)
+ r.setWidth(xDiff <= r.width() ? r.width() - xDiff : 0);
+ if (yDiff > 0)
+ r.setHeight(yDiff <= r.height() ? r.height() - yDiff : 0);
+ }
+
+ _activePage = page;
+ _field18 = 0;
+ _clipRect = r;
+ _setupFn = setupFn;
+ _addFn = addFn;
+ _restoreFn = restoreFn;
+
+ if (setupFn)
+ (_state._vm->_graphicsManager.*setupFn)(this);
+}
+
+void ViewPortResource::setupViewPort() {
+ setupViewPort(_state._vm->_graphicsManager._backgroundPage, NULL,
+ &GraphicsManager::setupMCGASaveRect, &GraphicsManager::addRectOptSaveRect,
+ &GraphicsManager::restoreMCGASaveRect);
+}
+
+void ViewPortResource::setupViewPort(PictureResource *pic, Common::Rect *clipRect) {
+ setupViewPort(pic, clipRect,
+ &GraphicsManager::setupMCGASaveRect, &GraphicsManager::addRectOptSaveRect,
+ &GraphicsManager::restoreMCGASaveRect);
+}
+
+void ViewPortResource::addSaveRect(int pageIndex, const Common::Rect &r) {
+ Common::Rect rect = r;
+
+ if (clipRect(rect)) {
+ if (_addFn) {
+ (_state._vm->_graphicsManager.*_addFn)(this, pageIndex, rect);
+ } else if (_rectListCount[pageIndex] != -1) {
+ _rectListPtr[pageIndex]->push_back(rect);
+ }
+ }
+}
+
+void ViewPortResource::fillPic(byte onOff) {
+ _state._vm->_graphicsManager.fillPic(this, onOff);
+}
+
+void ViewPortResource::drawIfaceTime() {
+ // Hour display
+ _state._vm->_graphicsManager.drawANumber(*_state._vm->_graphicsManager._vPort,
+ (_state._vm->_gameHour / 10) == 0 ? 10 : _state._vm->_gameHour / 10,
+ Common::Point(161, 25));
+ _state._vm->_graphicsManager.drawANumber(*_state._vm->_graphicsManager._vPort,
+ _state._vm->_gameHour % 10, Common::Point(172, 25));
+
+ // Minute display
+ _state._vm->_graphicsManager.drawANumber(*_state._vm->_graphicsManager._vPort,
+ _state._vm->_gameMinute / 10, Common::Point(190, 25));
+ _state._vm->_graphicsManager.drawANumber(*_state._vm->_graphicsManager._vPort,
+ _state._vm->_gameMinute % 10, Common::Point(201, 25));
+
+ // AM/PM indicator
+ PictureResource *pic = _state._vm->_bVoy->boltEntry(_state._vm->_voy._isAM ? 272 : 273)._picResource;
+ _state._vm->_graphicsManager.sDrawPic(pic, *_state._vm->_graphicsManager._vPort,
+ Common::Point(215, 27));
+}
+
+void ViewPortResource::drawPicPerm(PictureResource *pic, const Common::Point &pt) {
+ Common::Rect bounds = pic->_bounds;
+ bounds.translate(pt.x, pt.y);
+
+ bool saveBack = _state._vm->_graphicsManager._saveBack;
+ _state._vm->_graphicsManager._saveBack = false;
+ _state._vm->_graphicsManager.sDrawPic(pic, this, pt);
+ clipRect(bounds);
+
+ for (int pageIndex = 0; pageIndex < _pageCount; ++pageIndex) {
+ if (_pageIndex != pageIndex) {
+ addSaveRect(pageIndex, bounds);
+ }
+ }
+
+ _state._vm->_graphicsManager._saveBack = saveBack;
+}
+/*------------------------------------------------------------------------*/
+
+ViewPortListResource::ViewPortListResource(BoltFilesState &state, const byte *src) {
+ uint count = READ_LE_UINT16(src);
+ _palIndex = READ_LE_UINT16(src + 2);
+
+ // Load palette map
+ byte *palData = state._curLibPtr->memberAddr(READ_LE_UINT32(src + 4));
+ for (uint i = 0; i < 256; ++i, palData += 16)
+ _palette.push_back(ViewPortPalEntry(palData));
+
+ // Load view port pointer list
+ const uint32 *idP = (const uint32 *)&src[8];
+ for (uint i = 0; i < count; ++i, ++idP) {
+ uint32 id = READ_LE_UINT32(idP);
+ BoltEntry &entry = state._curLibPtr->getBoltEntryFromLong(id);
+
+ assert(entry._viewPortResource);
+ _entries.push_back(entry._viewPortResource);
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+ViewPortPalEntry::ViewPortPalEntry(const byte *src) {
+ const uint16 *v = (const uint16 *)src;
+ _rEntry = READ_LE_UINT16(v++);
+ _gEntry = READ_LE_UINT16(v++);
+ _bEntry = READ_LE_UINT16(v++);
+ _rChange = READ_LE_UINT16(v++);
+ _gChange = READ_LE_UINT16(v++);
+ _bChange = READ_LE_UINT16(v++);
+ _palIndex = READ_LE_UINT16(v++);
+}
+
+
+/*------------------------------------------------------------------------*/
+
+FontResource::FontResource(BoltFilesState &state, byte *src) {
+ _minChar = src[0];
+ _maxChar = src[1];
+ _fontDepth = src[2];
+ _padding = src[3];
+ _fontHeight = src[5];
+ _topPadding = (int8)src[6];
+
+ int totalChars = _maxChar - _minChar + 1;
+ _charWidth = new int[totalChars];
+ for (int i = 0; i < totalChars; ++i)
+ _charWidth[i] = READ_LE_UINT16(src + 8 + 2 * i);
+
+ _charOffsets = src + 8 + totalChars * 2;
+ _charImages = _charOffsets + totalChars * 2;
+}
+
+FontResource::~FontResource() {
+ delete[] _charWidth;
+}
+
+/*------------------------------------------------------------------------*/
+
+FontInfoResource::FontInfoResource(BoltFilesState &state, const byte *src) {
+ _curFont = NULL;
+ _picFlags = src[4];
+ _picSelect = src[5];
+ _picPick = src[6];
+ _picOnOff = src[7];
+ _fontFlags = src[8];
+ _justify = (FontJustify)src[9];
+ _fontSaveBack = READ_LE_UINT16(src + 10);
+ _pos.x = (int16)READ_LE_UINT16(src + 12);
+ _pos.y = (int16)READ_LE_UINT16(src + 14);
+ _justifyWidth = READ_LE_UINT16(src + 16);
+ _justifyHeight = READ_LE_UINT16(src + 18);
+ _shadow.x = READ_LE_UINT16(src + 20);
+ _shadow.y = READ_LE_UINT16(src + 22);
+ _foreColor = READ_LE_UINT16(src + 24);
+ _backColor = READ_LE_UINT16(src + 26);
+ _shadowColor = READ_LE_UINT16(src + 28);
+}
+
+FontInfoResource::FontInfoResource() {
+ _curFont = NULL;
+ _picFlags = 3;
+ _picSelect = 0xff;
+ _picPick = 0xff;
+ _picOnOff = 0;
+ _fontFlags = 0;
+ _justify = ALIGN_LEFT;
+ _fontSaveBack = 0;
+ _justifyWidth = 1;
+ _justifyHeight = 1;
+ _shadow = Common::Point(1, 1);
+ _foreColor = 1;
+ _backColor = 0;
+ _shadowColor = 0;
+}
+
+FontInfoResource::FontInfoResource(byte picFlags, byte picSelect, byte picPick, byte picOnOff,
+ byte fontFlags, FontJustify justify, int fontSaveBack, const Common::Point &pos,
+ int justifyWidth, int justifyHeight, const Common::Point &shadow, int foreColor,
+ int backColor, int shadowColor) {
+ _curFont = NULL;
+ _picFlags = picFlags;
+ _picSelect = picSelect;
+ _picPick = picPick;
+ _picOnOff = picOnOff;
+ _fontFlags = fontFlags;
+ _justify = justify;
+ _fontSaveBack = fontSaveBack;
+ _pos = pos;
+ _justifyWidth = justifyWidth;
+ _justifyHeight = justifyHeight;
+ _shadow = shadow;
+ _foreColor = foreColor;
+ _backColor = backColor;
+ _shadowColor = shadowColor;
+}
+
+/*------------------------------------------------------------------------*/
+
+CMapResource::CMapResource(BoltFilesState &state, const byte *src): _vm(state._vm) {
+ _steps = src[0];
+ _fadeStatus = src[1];
+ _start = READ_LE_UINT16(src + 2);
+ _end = READ_LE_UINT16(src + 4);
+
+ int count = _end - _start + 1;
+ _entries = new byte[count * 3];
+ Common::copy(src + 6, src + 6 + 3 * count, _entries);
+
+ int palIndex = state._vm->_graphicsManager._viewPortListPtr->_palIndex;
+ if (_end > palIndex)
+ _end = palIndex;
+ if (_start > palIndex)
+ _start = palIndex;
+}
+
+CMapResource::~CMapResource() {
+ delete[] _entries;
+}
+
+void CMapResource::startFade() {
+ _vm->_eventsManager.startFade(this);
+}
+
+/*------------------------------------------------------------------------*/
+
+VInitCycleResource::VInitCycleResource(BoltFilesState &state, const byte *src):
+ _state(state) {
+ // Set up arrays
+ for (int i = 0; i < 4; ++i) {
+ _type[i] = READ_LE_UINT16(src + i * 2);
+ state._curLibPtr->resolveIt(READ_LE_UINT32(src + 8 + i * 4), &_ptr[i]);
+ }
+}
+
+void VInitCycleResource::vStartCycle(int flags) {
+ EventsManager &evt = _state._vm->_eventsManager;
+ evt._cycleIntNode._flags |= 1;
+ evt._cyclePtr = this;
+
+ for (int i = 0; i < 4; ++i) {
+ evt._cycleNext[i] = _ptr[i];
+ evt._cycleTime[i] = 0;
+ }
+
+ evt._cycleStatus = flags | 1;
+ evt._cycleIntNode._flags &= ~1;
+}
+
+void VInitCycleResource::vStopCycle() {
+ EventsManager &evt = _state._vm->_eventsManager;
+ evt._cycleIntNode._flags |= 1;
+ evt._cycleStatus &= ~1;
+}
+
+/*------------------------------------------------------------------------*/
+
+PtrResource::PtrResource(BoltFilesState &state, const byte *src) {
+ // Load pointer list
+ const uint32 *idP = (const uint32 *)&src[0];
+ int size = state._curMemberPtr->_size;
+
+ for (int i = 0; i < size / 4; ++i, ++idP) {
+ uint32 id = READ_LE_UINT32(idP);
+ BoltEntry &entry = state._curLibPtr->getBoltEntryFromLong(id);
+
+ _entries.push_back(&entry);
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+ControlResource::ControlResource(BoltFilesState &state, const byte *src) {
+ // Get Id for the state data. Since it refers to a following entry in the same
+ // group, for simplicity we set the _state back in the main playStamp method
+ _stateId = READ_LE_UINT32(&src[0x32]);
+ _state = nullptr;
+
+ for (int i = 0; i < 8; ++i)
+ _memberIds[i] = READ_LE_UINT16(src + i * 2);
+
+ // Load pointer list
+ const uint32 *idP = (const uint32 *)&src[0x10];
+ int count = READ_LE_UINT16(&src[0x36]);
+
+ Common::fill(&_entries[0], &_entries[8], (byte *)nullptr);
+ for (int i = 0; i < count; ++i, ++idP) {
+ uint32 id = READ_LE_UINT32(idP);
+ state._curLibPtr->resolveIt(id, &_entries[i]);
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+StateResource::StateResource(BoltFilesState &state, const byte *src):
+ _victimIndex(_vals[1]), _victimEvidenceIndex(_vals[2]),
+ _victimMurderIndex(_vals[3]) {
+ for (int i = 0; i < 4; ++i)
+ _vals[i] = READ_LE_UINT32(src + i * 4);
+}
+
+void StateResource::synchronize(Common::Serializer &s) {
+ for (int i = 0; i < 4; ++i)
+ s.syncAsSint32LE(_vals[i]);
+}
+
+} // End of namespace Voyeur
diff --git a/engines/voyeur/files.h b/engines/voyeur/files.h
new file mode 100644
index 0000000000..69802d8f68
--- /dev/null
+++ b/engines/voyeur/files.h
@@ -0,0 +1,650 @@
+/* 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 VOYEUR_FILES_H
+#define VOYEUR_FILES_H
+
+#include "common/scummsys.h"
+#include "common/file.h"
+#include "common/rect.h"
+#include "common/str.h"
+#include "voyeur/graphics.h"
+
+namespace Voyeur {
+
+class VoyeurEngine;
+class BoltFile;
+class BoltGroup;
+class BoltEntry;
+class RectResource;
+class PictureResource;
+class ViewPortResource;
+class ViewPortListResource;
+class FontResource;
+class CMapResource;
+class VInitCycleResource;
+class PtrResource;
+class ControlResource;
+class StateResource;
+class ThreadResource;
+
+#define DECOMPRESS_SIZE 0x7000
+
+class ResolveEntry {
+public:
+ uint32 _id;
+ byte **_p;
+
+ ResolveEntry(uint32 id, byte **p) { _id = id; _p = p; }
+};
+
+class BoltFilesState {
+public:
+ VoyeurEngine *_vm;
+ BoltFile *_curLibPtr;
+ BoltGroup *_curGroupPtr;
+ BoltEntry *_curMemberPtr;
+ byte *_curMemInfoPtr;
+ int _fromGroupFlag;
+ byte _xorMask;
+ bool _encrypt;
+ int _curFilePosition;
+ int _bufferEnd;
+ int _bufferBegin;
+ int _bytesLeft;
+ int _bufSize;
+ byte *_bufStart;
+ byte *_bufPos;
+ byte _decompressBuf[DECOMPRESS_SIZE];
+ int _historyIndex;
+ byte _historyBuffer[0x200];
+ int _runLength;
+ int _decompState;
+ int _runType;
+ int _runValue;
+ int _runOffset;
+ Common::File *_curFd;
+ Common::Array<ResolveEntry> _resolves;
+
+ byte *_boltPageFrame;
+ int _sImageShift;
+ bool _SVGAReset;
+public:
+ BoltFilesState();
+
+ byte *decompress(byte *buf, int size, int mode);
+ void nextBlock();
+
+ // Methods in the original stubbed under ScummVM
+ void EMSGetFrameAddr(byte **pageFrame) {}
+ bool EMSAllocatePages(uint *planeSize) { return false; }
+ void EMSMapPageHandle(int planeSize, int idx1, int idx2) {}
+};
+
+class BoltFile {
+private:
+ Common::Array<BoltGroup> _groups;
+protected:
+ BoltFilesState &_state;
+
+ virtual void initResource(int resType) = 0;
+ void initDefault();
+private:
+ void resolveAll();
+ byte *getBoltMember(uint32 id);
+
+ // Methods in the original that are stubbed in ScummVM
+ void termType() {}
+ void initMem(int id) {}
+ void termMem() {}
+ void initGro() {}
+ void termGro() {}
+public:
+ Common::File _file;
+public:
+ BoltFile(const Common::String &filename, BoltFilesState &state);
+ virtual ~BoltFile();
+
+ BoltGroup *getBoltGroup(uint16 id, bool process = true);
+ void freeBoltGroup(uint16 id, bool freeEntries = true);
+ void freeBoltMember(uint32 id);
+ byte *memberAddr(uint32 id);
+ byte *memberAddrOffset(uint32 id);
+ void resolveIt(uint32 id, byte **p);
+ void resolveFunction(uint32 id, GraphicMethodPtr *fn);
+
+ BoltEntry &boltEntry(uint16 id);
+ BoltEntry &getBoltEntryFromLong(uint32 id);
+ PictureResource *getPictureResource(uint32 id);
+ CMapResource *getCMapResource(uint32 id);
+};
+
+class BVoyBoltFile: public BoltFile {
+private:
+ // initType method table
+ void sInitRect();
+ void sInitPic();
+ void vInitCMap();
+ void vInitCycl();
+ void initViewPort();
+ void initViewPortList();
+ void initFontInfo();
+ void initFont();
+ void initSoundMap();
+protected:
+ virtual void initResource(int resType);
+public:
+ BVoyBoltFile(BoltFilesState &state);
+};
+
+class StampBoltFile: public BoltFile {
+private:
+ void initThread();
+ void initState();
+ void initPtr();
+ void initControl();
+protected:
+ virtual void initResource(int resType);
+public:
+ StampBoltFile(BoltFilesState &state);
+};
+
+class BoltGroup {
+private:
+ Common::SeekableReadStream *_file;
+public:
+ byte _loaded;
+ bool _processed;
+ bool _callInitGro;
+ int _termGroIndex;
+ int _count;
+ int _fileOffset;
+ Common::Array<BoltEntry> _entries;
+public:
+ BoltGroup(Common::SeekableReadStream *f);
+ virtual ~BoltGroup();
+
+ void load(uint16 groupId);
+ void unload();
+};
+
+
+class BoltEntry {
+private:
+ Common::SeekableReadStream *_file;
+public:
+ uint16 _id;
+ byte _mode;
+ byte _initMemRequired;
+ byte _initMethod;
+ int _fileOffset;
+ byte _xorMask;
+ int _size;
+ byte *_data;
+
+ // bvoy.blt resource types
+ RectResource *_rectResource;
+ PictureResource *_picResource;
+ ViewPortResource *_viewPortResource;
+ ViewPortListResource *_viewPortListResource;
+ FontResource *_fontResource;
+ FontInfoResource *_fontInfoResource;
+ CMapResource *_cMapResource;
+ VInitCycleResource *_vInitCycleResource;
+
+ // stampblt.blt resource types
+ PtrResource *_ptrResource;
+ ControlResource *_controlResource;
+ StateResource *_stateResource;
+ ThreadResource *_threadResource;
+public:
+ BoltEntry(Common::SeekableReadStream *f, uint16 id);
+ virtual ~BoltEntry();
+
+ void load();
+ bool hasResource() const;
+};
+
+class FilesManager {
+private:
+ int _decompressSize;
+public:
+ BoltFilesState _boltFilesState;
+ BoltFile *_curLibPtr;
+public:
+ FilesManager();
+ void setVm(VoyeurEngine *vm) { _boltFilesState._vm = vm; }
+
+ bool openBoltLib(const Common::String &filename, BoltFile *&boltFile);
+ byte *fload(const Common::String &filename, int *size = NULL);
+};
+
+class RectEntry: public Common::Rect {
+public:
+ int _arrIndex;
+ int _count;
+
+ RectEntry(int x1, int y1, int x2, int y2, int arrIndex, int count);
+};
+
+class RectResource: public Common::Rect {
+public:
+ Common::Array<RectEntry> _entries;
+public:
+ RectResource(const byte *src, int size, bool isExtendedRects);
+ RectResource(int xp, int yp, int width, int height);
+ virtual ~RectResource() {}
+};
+
+enum DisplayFlag { DISPFLAG_1 = 1, DISPFLAG_2 = 2, DISPFLAG_4 = 4, DISPFLAG_8 = 8,
+ DISPFLAG_10 = 0x10, DISPFLAG_20 = 0x20, DISPFLAG_40 = 0x40, DISPFLAG_80 = 0x80,
+ DISPFLAG_100 = 0x100, DISPFLAG_200 = 0x200, DISPFLAG_400 = 0x400,
+ DISPFLAG_800 = 0x800, DISPFLAG_1000 = 0x1000, DISPFLAG_2000 = 0x2000,
+ DISPFLAG_4000 = 0x4000, DISPFLAG_VIEWPORT = 0x8000, DISPFLAG_CURSOR = 0x10000 };
+
+class DisplayResource {
+private:
+ VoyeurEngine *_vm;
+public:
+ uint32 _flags;
+public:
+ DisplayResource();
+ DisplayResource(VoyeurEngine *vm);
+
+ /**
+ * Fill a box of the given size at the current _drawPtr location
+ */
+ void sFillBox(int width, int height);
+
+ /**
+ * Draw text at the current pen position
+ */
+ int drawText(const Common::String &msg);
+
+ /**
+ * Return the width of a given text in the current font
+ */
+ int textWidth(const Common::String &msg);
+
+ /**
+ * Clip the given rectangle by the currently viewable area
+ */
+ bool clipRect(Common::Rect &rect);
+};
+
+/* bvoy.blt resource types */
+
+enum PictureFlag { PICFLAG_2 = 2, PICFLAG_PIC_OFFSET = 8, PICFLAG_CLEAR_SCREEN = 0x10,
+ PICFLAG_20 = 0x20, PICFLAG_HFLIP = 0x40, PICFLAG_VFLIP = 0x80, PICFLAG_100 = 0x100,
+ PICFLAG_CLEAR_SCREEN00 = 0x1000
+};
+
+class PictureResource: public DisplayResource {
+private:
+ /**
+ * Flip the image data horizontally
+ */
+ void flipHorizontal(const byte *data);
+
+ /**
+ * Flip the image data vertically
+ */
+ void flipVertical(const byte *data);
+public:
+ byte _select;
+ byte _pick;
+ byte _onOff;
+ byte _depth;
+ Common::Rect _bounds;
+ uint32 _maskData;
+ uint _planeSize;
+
+ /**
+ * Image data for the picture
+ */
+ byte *_imgData;
+
+ /**
+ * Flag to indicate whether to free the image data
+ */
+ DisposeAfterUse::Flag _freeImgData;
+public:
+ PictureResource(BoltFilesState &state, const byte *src);
+ PictureResource(int flags, int select, int pick, int onOff, int depth,
+ const Common::Rect &bounds, int maskData, byte *imgData, int planeSize);
+ PictureResource(Graphics::Surface *surface);
+ PictureResource();
+ virtual ~PictureResource();
+};
+
+typedef void (ViewPortResource::*ViewPortMethodPtr)();
+
+class ViewPortResource: public DisplayResource {
+private:
+ BoltFilesState &_state;
+private:
+ void setupViewPort(PictureResource *page, Common::Rect *clipRect, ViewPortSetupPtr setupFn,
+ ViewPortAddPtr addFn, ViewPortRestorePtr restoreFn);
+public:
+ ViewPortResource *_parent;
+ int _pageCount;
+ int _pageIndex;
+ int _lastPage;
+ Common::Rect _bounds;
+ int _field18; // Useless variable
+ PictureResource *_currentPic;
+ PictureResource *_activePage;
+ PictureResource *_pages[2];
+ byte *_field30;
+
+ // Rect lists and counts. Note that _rectListCount values of '-1' seem to have
+ // special significance, which is why I'm not making them redundant in favour
+ // of the arrays' .size() method
+ Common::Array<Common::Rect> *_rectListPtr[3];
+ int _rectListCount[3];
+
+ Common::Rect _clipRect;
+ byte *_field7A;
+ GraphicMethodPtr _fn1;
+ ViewPortSetupPtr _setupFn;
+ ViewPortAddPtr _addFn;
+ ViewPortRestorePtr _restoreFn;
+ Common::Rect _fontRect;
+public:
+ ViewPortResource(BoltFilesState &state, const byte *src);
+ virtual ~ViewPortResource();
+
+ void setupViewPort();
+ void setupViewPort(PictureResource *pic, Common::Rect *clipRect = NULL);
+ void addSaveRect(int pageIndex, const Common::Rect &r);
+ void fillPic(byte onOff = 0);
+ void drawIfaceTime();
+ void drawPicPerm(PictureResource *pic, const Common::Point &pt);
+};
+
+class ViewPortPalEntry {
+public:
+ uint16 _rEntry, _gEntry, _bEntry;
+ uint16 _rChange, _gChange, _bChange;
+ uint16 _palIndex;
+public:
+ ViewPortPalEntry(const byte *src);
+};
+
+class ViewPortListResource {
+public:
+ Common::Array<ViewPortPalEntry> _palette;
+ Common::Array<ViewPortResource *> _entries;
+ int _palIndex;
+
+ ViewPortListResource(BoltFilesState &state, const byte *src);
+ virtual ~ViewPortListResource() {}
+};
+
+class FontResource {
+public:
+ int _minChar, _maxChar;
+ int _fontDepth;
+ int _padding;
+ int _fontHeight;
+ int _topPadding;
+ int *_charWidth;
+ byte *_charOffsets;
+ byte *_charImages;
+
+ FontResource(BoltFilesState &state, byte *src);
+ virtual ~FontResource();
+};
+
+enum FontJustify { ALIGN_LEFT = 0, ALIGN_CENTRE = 1, ALIGN_RIGHT = 2 };
+
+class FontInfoResource {
+public:
+ FontResource *_curFont;
+ byte _picFlags;
+ byte _picSelect;
+ byte _picPick;
+ byte _picOnOff;
+ byte _fontFlags;
+ FontJustify _justify;
+ int _fontSaveBack;
+ Common::Point _pos;
+ int _justifyWidth;
+ int _justifyHeight;
+ Common::Point _shadow;
+ int _foreColor;
+ int _backColor;
+ int _shadowColor;
+public:
+ FontInfoResource(BoltFilesState &state, const byte *src);
+ FontInfoResource();
+ FontInfoResource(byte picFlags, byte picSelect, byte picPick, byte picOnOff, byte fontFlags,
+ FontJustify justify, int fontSaveBack, const Common::Point &pos, int justifyWidth,
+ int justifyHeight, const Common::Point &shadow, int foreColor, int backColor,
+ int shadowColor);
+};
+
+class CMapResource {
+private:
+ VoyeurEngine *_vm;
+public:
+ int _steps;
+ int _fadeStatus;
+ int _start;
+ int _end;
+ byte *_entries;
+public:
+ CMapResource(BoltFilesState &state, const byte *src);
+ virtual ~CMapResource();
+
+ void startFade();
+};
+
+class VInitCycleResource {
+private:
+ BoltFilesState &_state;
+public:
+ int _type[4];
+ byte *_ptr[4];
+public:
+ VInitCycleResource(BoltFilesState &state, const byte *src);
+ virtual ~VInitCycleResource() {}
+
+ void vStartCycle(int flags = 0);
+ void vStopCycle();
+};
+
+/* stampblt.blt resources */
+
+class PtrResource {
+public:
+ Common::Array<BoltEntry *> _entries;
+
+ PtrResource(BoltFilesState &state, const byte *src);
+ virtual ~PtrResource() {}
+};
+
+class ControlResource {
+public:
+ int _memberIds[8];
+ byte *_entries[8];
+ int _stateId;
+ StateResource *_state;
+
+ ControlResource(BoltFilesState &state, const byte *src);
+ virtual ~ControlResource() {}
+};
+
+/**
+ * Stores data about the intended victim
+ */
+class StateResource {
+public:
+ int _vals[4];
+ int &_victimIndex;
+ int &_victimEvidenceIndex;
+ int &_victimMurderIndex;
+
+ StateResource(BoltFilesState &state, const byte *src);
+ virtual ~StateResource() {}
+
+ /**
+ * Synchronizes the game data
+ */
+ void synchronize(Common::Serializer &s);
+};
+
+class ThreadResource {
+public:
+ static int _useCount[8];
+ static byte *_threadDataPtr;
+ static CMapResource *_cmd14Pal;
+ static void initUseCount();
+ static void unloadAllStacks(VoyeurEngine *vm);
+
+ static void init();
+private:
+ VoyeurEngine *_vm;
+ Common::Point _aptPos;
+private:
+ bool getStateInfo();
+ byte *getDataOffset();
+ void getButtonsText();
+ void getButtonsFlags();
+ void getButtonsUnused();
+ void performOpenCard();
+ const byte *getRecordOffset(const byte *p);
+ const byte *getNextRecord(const byte *p);
+ const byte *getSTAMPCard(int cardId);
+ int getStateFromID(uint32 id);
+ uint32 getSID(int sid);
+ void cardAction(const byte *p);
+ void doSTAMPCardAction();
+ bool goToStateID(int stackId, int id);
+ const byte *cardPerform(const byte *card);
+ bool cardPerform2(const byte *p, int cardCmdId);
+ void savePrevious();
+ void setButtonFlag(int idx, byte bits);
+ void clearButtonFlag(int idx, byte bits);
+
+ /**
+ * Frees the apartment screen data
+ */
+ void freeTheApt();
+
+ /**
+ * Does any necessary animation at the start or end of showing the apartment.
+ */
+ void doAptAnim(int mode);
+
+ /**
+ * Updates the mansion scroll position if ncessary, and returns true if it
+ * has been changed.
+ */
+ bool checkMansionScroll();
+public:
+ int _stateId;
+ int _stackId;
+ int _savedStateId;
+ int _savedStackId;
+ int _newStateId;
+ int _newStackId;
+ int _flags;
+ int _fieldA[8]; // Useless variable
+ int _field2A[8]; // Useless variable
+ int _stateFlags;
+ int _stateCount;
+ int _parseCount;
+ uint32 _nextStateId;
+ byte *_threadInfoPtr;
+ byte _buttonFlags[64];
+ const byte *_field8E[64]; // Useless variable
+ byte _buttonIds[64];
+ const byte *_buttonUnused[48];
+ byte *_ctlPtr;
+ byte *_playCommandsPtr;
+public:
+ ThreadResource(BoltFilesState &state, const byte *src);
+ virtual ~ThreadResource() {}
+
+ /**
+ * Initialize the thread
+ */
+ void initThreadStruct(int idx, int id);
+
+ /**
+ * Loads the specified stack
+ */
+ bool loadAStack(int stackId);
+
+ /**
+ * Unloads the specified stack
+ */
+ void unloadAStack(int stackId);
+
+ /**
+ * Go to a new state and/or stack
+ */
+ bool goToState(int stackId, int stateId);
+
+ /**
+ * Initializes data for the thread based on the current state
+ */
+ bool doState();
+
+ bool chooseSTAMPButton(int buttonId);
+
+ /**
+ * Parses the script commands from the currently active stack
+ */
+ void parsePlayCommands();
+
+ /**
+ * Do the camera view looking at the mansion
+ */
+ int doInterface();
+
+ /**
+ * Do the display of a room that has one or more evidence hotspots
+ * available for display
+ */
+ void doRoom();
+
+ /**
+ * Shows the apartment screen
+ */
+ int doApt();
+
+ /**
+ * Loads data needed for displaying the initial apartment screen
+ */
+ void loadTheApt();
+
+ /**
+ * Synchronizes the game data
+ */
+ void synchronize(Common::Serializer &s);
+};
+
+} // End of namespace Voyeur
+
+#endif /* VOYEUR_FILES_H */
diff --git a/engines/voyeur/files_threads.cpp b/engines/voyeur/files_threads.cpp
new file mode 100644
index 0000000000..5989c92439
--- /dev/null
+++ b/engines/voyeur/files_threads.cpp
@@ -0,0 +1,1738 @@
+/* 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 "voyeur/files.h"
+#include "voyeur/graphics.h"
+#include "voyeur/voyeur.h"
+#include "voyeur/staticres.h"
+
+namespace Voyeur {
+
+int ThreadResource::_useCount[8];
+byte *ThreadResource::_threadDataPtr;
+CMapResource *ThreadResource::_cmd14Pal;
+
+void ThreadResource::init() {
+ Common::fill(&_useCount[0], &_useCount[8], 0);
+ _threadDataPtr = nullptr;
+ _cmd14Pal = nullptr;
+}
+
+ThreadResource::ThreadResource(BoltFilesState &state, const byte *src):
+ _vm(state._vm) {
+ _stateId = READ_LE_UINT16(&src[0]);
+ _stackId = READ_LE_UINT16(&src[0]);
+ _savedStateId = READ_LE_UINT16(&src[0]);
+ _savedStackId = READ_LE_UINT16(&src[0]);
+ _flags = src[8];
+ _ctlPtr = nullptr;
+ _aptPos = Common::Point(-1, -1);
+}
+
+void ThreadResource::initThreadStruct(int idx, int id) {
+ _stackId = -1;
+ if (loadAStack(idx)) {
+ _savedStateId = _savedStackId = -1;
+ _stateId = id;
+ _newStateId = -1;
+ _newStackId = -1;
+
+ doState();
+ }
+}
+
+bool ThreadResource::loadAStack(int stackId) {
+ if (_vm->_stampFlags & 1) {
+ unloadAStack(_stackId);
+ if (!_useCount[stackId]) {
+ BoltEntry &boltEntry = _vm->_stampLibPtr->boltEntry(_vm->_controlPtr->_memberIds[stackId]);
+ if (!boltEntry._data)
+ return false;
+
+ _vm->_controlPtr->_entries[stackId] = boltEntry._data;
+ }
+
+ ++_useCount[stackId];
+ }
+
+ _ctlPtr = _vm->_controlPtr->_entries[stackId];
+ _stackId = stackId;
+ return true;
+}
+
+void ThreadResource::unloadAStack(int stackId) {
+ if ((_vm->_stampFlags & 1) && _useCount[stackId]) {
+ if (--_useCount[stackId] == 0) {
+ _vm->_stampLibPtr->freeBoltMember(_vm->_controlPtr->_memberIds[stackId]);
+ }
+ }
+}
+
+bool ThreadResource::doState() {
+ _flags |= 1;
+
+ if (!getStateInfo())
+ return false;
+
+ getButtonsFlags();
+ getButtonsUnused();
+
+ _vm->_glGoState = -1;
+ _vm->_glGoStack = -1;
+
+ performOpenCard();
+ if (_stateFlags & 1) {
+ return chooseSTAMPButton(_vm->getRandomNumber(_stateCount - 1));
+ } else {
+ return true;
+ }
+}
+
+bool ThreadResource::getStateInfo() {
+ _flags &= 0xff;
+ int id = READ_LE_UINT16(_ctlPtr);
+
+ if (id <= _stateId) {
+ _flags |= 0x8000;
+ return false;
+ } else {
+ uint32 fld = READ_LE_UINT32(_ctlPtr + 2);
+ fld += _stateId << 3;
+ _nextStateId = READ_LE_UINT32(_ctlPtr + fld + 4);
+
+ fld = READ_LE_UINT32(_ctlPtr + fld);
+ byte *baseP = _ctlPtr + fld;
+ _stateCount = READ_LE_UINT16(baseP);
+ _stateFlags = READ_LE_UINT16(baseP + 2);
+ _parseCount = READ_LE_UINT16(baseP + 4);
+
+ _playCommandsPtr = getDataOffset();
+ _playCommandsPtr += (READ_LE_UINT32(baseP + 6) / 2) << 1;
+
+ _threadInfoPtr = baseP + 10;
+
+ getButtonsText();
+ return true;
+ }
+}
+
+byte *ThreadResource::getDataOffset() {
+ uint32 offset = READ_LE_UINT32(_ctlPtr + 10);
+ _threadDataPtr = _ctlPtr + offset;
+ return _threadDataPtr;
+}
+
+void ThreadResource::getButtonsText() {
+ int idx = 0;
+
+ for (const byte *p = _threadInfoPtr; *p != 0x49; p = getNextRecord(p)) {
+ if (*p == 0xC0) {
+ ++p;
+ if (*p++ & 0x80) {
+ assert(idx < 63);
+ _field8E[idx] = getRecordOffset(p);
+ p += 4;
+ }
+
+ ++idx;
+ _field8E[idx] = NULL;
+ }
+ }
+}
+
+void ThreadResource::getButtonsFlags() {
+ int idx = 0;
+
+ for (const byte *p = _threadInfoPtr; *p != 0x49; p = getNextRecord(p)) {
+ if (*p == 0xC0) {
+ if (*++p & 0x20)
+ _stateFlags |= 2;
+
+ _buttonFlags[idx] = *p++;
+ _buttonIds[idx] = *p++;
+
+ if (_buttonFlags[idx] & 0x80)
+ p += 4;
+
+ ++idx;
+ }
+ }
+}
+
+void ThreadResource::getButtonsUnused() {
+ int idx = 0;
+
+ for (const byte *p = _threadInfoPtr; *p++ != 0x4A; p = getNextRecord(p)) {
+ assert(idx < 47);
+ _buttonUnused[idx++] = getRecordOffset(p);
+ _buttonUnused[idx] = nullptr;
+ p += 4;
+ }
+}
+
+void ThreadResource::unloadAllStacks(VoyeurEngine *vm) {
+ if (vm->_stampFlags & 1) {
+ for (int i = 0; i < 8; ++i) {
+ if (_useCount[i])
+ vm->_stampLibPtr->freeBoltMember(vm->_controlPtr->_memberIds[i]);
+ }
+ }
+}
+
+void ThreadResource::performOpenCard() {
+ for (const byte *p = _threadInfoPtr; *p != 0x49; p = getNextRecord(p)) {
+ if (*p == 0x47) {
+ cardAction(p + 1);
+ return;
+ }
+ }
+}
+
+void ThreadResource::initUseCount() {
+ Common::fill(&_useCount[0], &_useCount[8], 0);
+}
+
+const byte *ThreadResource::getRecordOffset(const byte *p) {
+ uint32 recSize = READ_LE_UINT32(p) + READ_LE_UINT32(_ctlPtr + 6);
+ return _ctlPtr + recSize;
+}
+
+const byte *ThreadResource::getNextRecord(const byte *p) {
+ byte v = *p++;
+
+ switch (v) {
+ case 2:
+ case 4:
+ case 6:
+ case 8:
+ case 10:
+ return p + 8;
+ case 1:
+ case 3:
+ case 5:
+ case 7:
+ case 9:
+ case 11:
+ case 21:
+ case 22:
+ case 25:
+ case 26:
+ return p + 5;
+ case 17:
+ case 23:
+ case 24:
+ case 27:
+ case 28:
+ return p + 2;
+ case 19:
+ case 41:
+ return p + 6;
+ case 18:
+ case 51:
+ case 52:
+ return p + 1;
+ case 74:
+ return p + 4;
+ case 192:
+ if (*p & 0x80)
+ p += 4;
+ return p + 2;
+ default:
+ return p;
+ }
+}
+
+const byte *ThreadResource::getSTAMPCard(int cardId) {
+ const byte *p;
+ int count = 0;
+
+ for (p = _threadInfoPtr; count <= cardId && *p != 0x49; p = getNextRecord(p)) {
+ if (*p == 0xC0)
+ ++count;
+ }
+
+ return p;
+}
+
+int ThreadResource::getStateFromID(uint32 id) {
+ int count = READ_LE_UINT16(_ctlPtr);
+
+ for (int i = 0; i < count; ++i) {
+ uint32 sid = getSID(i);
+ if (sid == id)
+ return i;
+ }
+
+ return -1;
+}
+
+uint32 ThreadResource::getSID(int sid) {
+ uint32 offset = READ_LE_UINT32(_ctlPtr + 2) + (sid << 3) + 4;
+ return READ_LE_UINT32(_ctlPtr + offset);
+}
+
+void ThreadResource::doSTAMPCardAction() {
+ for (const byte *p = _threadInfoPtr; *p != 0x49; p = getNextRecord(p)) {
+ if (*p == 0x48) {
+ cardAction(p + 1);
+ return;
+ }
+ }
+}
+
+void ThreadResource::cardAction(const byte *card) {
+ _vm->_glGoState = -1;
+ _vm->_glGoStack = -1;
+
+ // Loop to perform card commands
+ while (!_vm->shouldQuit() && *card < 70 && _vm->_glGoState == -1) {
+ card = cardPerform(card);
+ }
+}
+
+bool ThreadResource::chooseSTAMPButton(int buttonId) {
+ _flags &= ~1;
+
+ for (int idx = 0; idx < _stateCount; ++idx) {
+ if (_buttonIds[idx] == buttonId) {
+ const byte *card = getSTAMPCard(idx);
+ cardAction(card);
+
+ bool flag = true;
+ while (!_vm->shouldQuit() && _vm->_glGoStack != -1 && flag) {
+ doSTAMPCardAction();
+ flag = goToStateID(_vm->_glGoStack, _vm->_glGoState);
+ }
+
+ while (!_vm->shouldQuit() && _vm->_glGoState != -1 && flag) {
+ doSTAMPCardAction();
+ flag = goToState(-1, _vm->_glGoState);
+ }
+
+ return flag;
+ }
+ }
+
+ return false;
+}
+
+void ThreadResource::parsePlayCommands() {
+ _vm->_voy._playStampMode = -1;
+ _vm->_voy._audioVisualStartTime = 0;
+ _vm->_voy._audioVisualDuration = 0;
+ _vm->_voy._boltGroupId2 = -1;
+ _vm->_voy._computerTextId = -1;
+ _vm->_voy._eventFlags &= ~EVTFLAG_8;
+ _vm->_eventsManager._videoDead = -1;
+
+ // Reset hotspot data
+ _vm->_voy._videoHotspotTimes.reset();
+ _vm->_voy._audioHotspotTimes.reset();
+ _vm->_voy._evidenceHotspotTimes.reset();
+ Common::fill(&_vm->_voy._roomHotspotsEnabled[0], &_vm->_voy._roomHotspotsEnabled[20], false);
+ byte *dataP = _playCommandsPtr;
+ int v2, v3;
+ PictureResource *pic;
+ CMapResource *pal;
+
+ for (int parseIndex = 0; parseIndex < _parseCount; ++parseIndex) {
+ uint16 id = READ_LE_UINT16(dataP);
+ debugC(DEBUG_BASIC, kDebugScripts, "parsePlayCommands (%d of %d) - cmd #%d",
+ parseIndex + 1, _parseCount, id);
+ dataP += 2;
+
+ switch (id) {
+ case 1:
+ _vm->_currentVocId = READ_LE_UINT16(dataP);
+ dataP += 2;
+ break;
+
+ case 2:
+ // Play an audio event
+ v2 = READ_LE_UINT16(dataP);
+
+ if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2) {
+ _vm->_audioVideoId = READ_LE_UINT16(dataP + 2) - 1;
+ _vm->_voy._audioVisualStartTime = READ_LE_UINT16(dataP + 4);
+ _vm->_voy._audioVisualDuration = READ_LE_UINT16(dataP + 6);
+
+ if (_vm->_voy._RTVNum < _vm->_voy._audioVisualStartTime ||
+ (_vm->_voy._audioVisualStartTime + _vm->_voy._audioVisualDuration) < _vm->_voy._RTVNum) {
+ _vm->_audioVideoId = -1;
+ } else {
+ _vm->_voy._vocSecondsOffset = _vm->_voy._RTVNum - _vm->_voy._audioVisualStartTime;
+ _vm->_voy.addAudioEventStart();
+
+ // Play the audio
+ assert(_vm->_audioVideoId < 38);
+ _vm->playAudio(_vm->_audioVideoId);
+
+ _vm->_voy.addAudioEventEnd();
+ _vm->_eventsManager.incrementTime(1);
+ _vm->_eventsManager.incrementTime(1);
+ _vm->_audioVideoId = -1;
+ parseIndex = 999;
+ }
+ }
+
+ dataP += 8;
+ break;
+
+ case 3:
+ // Play a video event
+ v2 = READ_LE_UINT16(dataP);
+
+ if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2) {
+ _vm->_audioVideoId = READ_LE_UINT16(dataP + 2) - 1;
+ _vm->_voy._audioVisualStartTime = READ_LE_UINT16(dataP + 4);
+ _vm->_voy._audioVisualDuration = READ_LE_UINT16(dataP + 6);
+
+ if (_vm->_voy._RTVNum < _vm->_voy._audioVisualStartTime ||
+ (_vm->_voy._audioVisualStartTime + _vm->_voy._audioVisualDuration) < _vm->_voy._RTVNum) {
+ _vm->_audioVideoId = -1;
+ } else {
+ _vm->_voy._vocSecondsOffset = _vm->_voy._RTVNum - _vm->_voy._audioVisualStartTime;
+ _vm->_voy.addVideoEventStart();
+ _vm->_voy._eventFlags &= ~EVTFLAG_TIME_DISABLED;
+ _vm->_voy._eventFlags |= EVTFLAG_RECORDING;
+ _vm->playAVideo(_vm->_audioVideoId);
+
+ _vm->_voy._eventFlags &= ~EVTFLAG_RECORDING;
+ _vm->_voy._eventFlags |= EVTFLAG_TIME_DISABLED;
+ _vm->_voy.addVideoEventEnd();
+ _vm->_eventsManager.incrementTime(1);
+
+ _vm->_audioVideoId = -1;
+ _vm->_playStampGroupId = -1;
+
+ if (_vm->_eventsManager._videoDead != -1) {
+ _vm->_bVoy->freeBoltGroup(0xE00);
+ _vm->_eventsManager._videoDead = -1;
+ _vm->flipPageAndWait();
+ }
+
+ _vm->_eventsManager._videoDead = -1;
+ if (_stateCount == 2 && _vm->_eventsManager._mouseClicked == 0) {
+ _vm->_voy._playStampMode = 132;
+ parseIndex = 999;
+ } else {
+ _vm->_voy._playStampMode = 129;
+ }
+ }
+ }
+
+ dataP += 8;
+ break;
+
+ case 4:
+ case 22:
+ // Case 22: Endgame news reports
+ _vm->_audioVideoId = READ_LE_UINT16(dataP) - 1;
+ dataP += 2;
+
+ if (id == 22) {
+ int resolveIndex = READ_LE_UINT16(dataP);
+ dataP += 2;
+ _vm->_playStampGroupId = _vm->_resolvePtr[resolveIndex];
+ }
+
+ _vm->_voy._vocSecondsOffset = 0;
+ _vm->_voy._audioVisualStartTime = _vm->_voy._RTVNum;
+ _vm->_voy._eventFlags &= ~(EVTFLAG_TIME_DISABLED | EVTFLAG_RECORDING);
+ _vm->playAVideo(_vm->_audioVideoId);
+ _vm->_voy._eventFlags |= EVTFLAG_TIME_DISABLED;
+
+ if (id != 22) {
+ _vm->_audioVideoId = -1;
+ parseIndex = 999;
+ } else {
+ int count = _vm->_bVoy->getBoltGroup(_vm->_playStampGroupId)->_entries.size() / 2;
+ _vm->_soundManager.stopVOCPlay();
+ _vm->_eventsManager.getMouseInfo();
+
+ for (int i = 0; i < count; ++i) {
+ pic = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + i * 2)._picResource;
+ pal = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + i * 2 + 1)._cMapResource;
+
+ (*_vm->_graphicsManager._vPort)->setupViewPort(pic);
+ pal->startFade();
+
+ _vm->flipPageAndWaitForFade();
+
+ if (i > 0) {
+ _vm->_bVoy->freeBoltMember(_vm->_playStampGroupId + i * 2);
+ _vm->_bVoy->freeBoltMember(_vm->_playStampGroupId + i * 2 + 1);
+ }
+
+ Common::String file = Common::String::format("news%d.voc", i + 1);
+ _vm->_soundManager.startVOCPlay(file);
+
+ while (!_vm->shouldQuit() && !_vm->_eventsManager._mouseClicked &&
+ _vm->_soundManager.getVOCStatus()) {
+ _vm->_eventsManager.delayClick(1);
+ _vm->_eventsManager.getMouseInfo();
+ }
+
+ _vm->_soundManager.stopVOCPlay();
+
+ if (i == (count - 1))
+ _vm->_eventsManager.delayClick(480);
+
+ if (_vm->shouldQuit() || _vm->_eventsManager._mouseClicked)
+ break;
+ }
+
+ _vm->_bVoy->freeBoltGroup(_vm->_playStampGroupId);
+ _vm->_playStampGroupId = -1;
+ _vm->_audioVideoId = -1;
+ parseIndex = 999;
+ }
+ break;
+
+ case 5:
+ // Check whether transition to a given time period is allowed, and
+ // if so, load the time information for the new time period
+ v2 = READ_LE_UINT16(dataP);
+ if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2) {
+ _vm->_voy._playStampMode = 5;
+ int count = READ_LE_UINT16(dataP + 2);
+ _vm->_voy._RTVLimit = READ_LE_UINT16(dataP + 4);
+
+ if (_vm->_voy._transitionId != count) {
+ if (_vm->_voy._transitionId > 1)
+ _vm->_voy._eventFlags &= ~EVTFLAG_100;
+
+ _vm->_voy._transitionId = count;
+ _vm->_gameMinute = LEVEL_M[count - 1];
+ _vm->_gameHour = LEVEL_H[count - 1];
+ //_vm->_v2A0A2 = 0;
+ _vm->_voy._RTVNum = 0;
+ _vm->_voy._RTANum = 255;
+ }
+
+ _vm->_voy._isAM = (_vm->_voy._transitionId == 6);
+ }
+
+ dataP += 6;
+ break;
+
+ case 6:
+ _vm->_voy._playStampMode = 6;
+ v2 = READ_LE_UINT16(dataP);
+ _vm->_playStampGroupId = _vm->_resolvePtr[v2];
+ dataP += 2;
+ break;
+
+ case 7:
+ // Load the video event scene hotspot times data
+ v2 = READ_LE_UINT16(dataP);
+ v3 = READ_LE_UINT16(dataP + 2) - 1;
+
+ if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2) {
+ int idx = 0;
+ while (_vm->_voy._videoHotspotTimes._min[idx][v3] != 9999)
+ ++idx;
+
+ v2 = READ_LE_UINT16(dataP + 4);
+ _vm->_voy._videoHotspotTimes._min[idx][v3] = v2;
+ _vm->_voy._videoHotspotTimes._max[idx][v3] = v2 + READ_LE_UINT16(dataP + 6) - 2;
+ }
+
+ dataP += 8;
+ break;
+
+ case 8:
+ // Load the audio event scene hotspot times data
+ v2 = READ_LE_UINT16(dataP);
+ v3 = READ_LE_UINT16(dataP + 2) - 1;
+
+ if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2) {
+ int idx = 0;
+ while (_vm->_voy._audioHotspotTimes._min[idx][v3] != 9999)
+ ++idx;
+
+ v2 = READ_LE_UINT16(dataP + 4);
+ _vm->_voy._audioHotspotTimes._min[idx][v3] = v2;
+ _vm->_voy._audioHotspotTimes._max[idx][v3] = v2 + READ_LE_UINT16(dataP + 6) - 2;
+ }
+
+ dataP += 8;
+ break;
+
+ case 9:
+ // Load up evidence event scene hotspot times data
+ v2 = READ_LE_UINT16(dataP);
+ v3 = READ_LE_UINT16(dataP + 2) - 1;
+
+ if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2) {
+ int idx = 0;
+ while (_vm->_voy._evidenceHotspotTimes._min[idx][v3] != 9999)
+ ++idx;
+
+ v2 = READ_LE_UINT16(dataP + 4);
+ _vm->_voy._evidenceHotspotTimes._min[idx][v3] = v2;
+ _vm->_voy._evidenceHotspotTimes._max[idx][v3] = v2 + READ_LE_UINT16(dataP + 6) - 2;
+ }
+
+ dataP += 8;
+ break;
+
+ case 10:
+ // Pick the person who is to die, during startup
+ if (_vm->_iForceDeath == -1) {
+ // No specific person has been preset to be killed, so pick one randomly.
+ // The loop below was used because the victim was persisted from the previous
+ // play-through, so it ensured that a different victim is picked.
+ int randomVal;
+ do {
+ randomVal = _vm->getRandomNumber(3) + 1;
+ } while (randomVal == _vm->_voy._victimNumber);
+
+ _vm->_voy._victimNumber = randomVal;
+ _vm->_controlPtr->_state->_victimIndex = randomVal;
+ } else {
+ // Player has seen something that locks in the character to die
+ _vm->_voy._victimNumber = _vm->_iForceDeath;
+ _vm->_controlPtr->_state->_victimIndex = _vm->_iForceDeath;
+ }
+
+ _vm->saveLastInplay();
+ break;
+
+ case 11:
+ _vm->_voy._eventFlags |= EVTFLAG_2;
+ break;
+
+ case 12:
+ v2 = READ_LE_UINT16(dataP);
+
+ if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2) {
+ _vm->_voy._boltGroupId2 = _vm->_resolvePtr[READ_LE_UINT16(dataP + 2)];
+ _vm->_voy._roomHotspotsEnabled[READ_LE_UINT16(dataP + 4) - 1] = true;
+ }
+
+ dataP += 6;
+ break;
+
+ case 13:
+ v2 = READ_LE_UINT16(dataP);
+
+ if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2) {
+ _vm->_voy._computerTextId = READ_LE_UINT16(dataP + 2);
+ _vm->_voy._computerTimeMin = READ_LE_UINT16(dataP + 4);
+ _vm->_voy._computerTimeMax = READ_LE_UINT16(dataP + 6);
+
+ _vm->_voy._rect4E4.left = COMP_BUT_TABLE[_vm->_voy._computerTextId * 4];
+ _vm->_voy._rect4E4.top = COMP_BUT_TABLE[_vm->_voy._computerTextId * 4 + 1];
+ _vm->_voy._rect4E4.right = COMP_BUT_TABLE[_vm->_voy._computerTextId * 4 + 2];
+ _vm->_voy._rect4E4.bottom = COMP_BUT_TABLE[_vm->_voy._computerTextId * 4 + 3];
+ }
+
+ dataP += 8;
+ break;
+
+ case 14:
+ _vm->_playStampGroupId = 2048;
+ _vm->_voy._playStampMode = 130;
+ break;
+
+ case 15:
+ _vm->showEndingNews();
+ break;
+
+ case 16:
+ _vm->_voy._playStampMode = 16;
+ break;
+
+ case 17:
+ _vm->_voy._playStampMode = 17;
+ break;
+
+ case 18:
+ // Called during the murder (Sunday 10:30PM) time period, to specify the
+ // time expired point at which the murder takes place
+ v2 = READ_LE_UINT16(dataP);
+ v3 = READ_LE_UINT16(dataP + 2);
+
+ if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2)
+ _vm->_voy._murderThreshold = v3;
+
+ dataP += 4;
+ break;
+
+ case 19:
+ _vm->_voy._aptLoadMode = 140;
+ loadTheApt();
+ _vm->_voy._aptLoadMode = 141;
+ freeTheApt();
+ break;
+
+ case 20:
+ _vm->_voy._aptLoadMode = -1;
+ loadTheApt();
+ _vm->_voy._aptLoadMode = 141;
+ freeTheApt();
+ break;
+
+ case 21:
+ _vm->_voy._aptLoadMode = -1;
+ loadTheApt();
+ _vm->_voy._aptLoadMode = 140;
+ freeTheApt();
+ break;
+
+ case 23:
+ _vm->_voy._transitionId = 17;
+ _vm->_voy._aptLoadMode = -1;
+ loadTheApt();
+ _vm->_voy._aptLoadMode = 144;
+ freeTheApt();
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+const byte *ThreadResource::cardPerform(const byte *card) {
+ uint16 id = *card++;
+ int varD = 5;
+ uint32 v2;
+ byte bVal;
+ uint32 idx1, idx2;
+ debugC(DEBUG_BASIC, kDebugScripts, "cardPerform - %d", id);
+
+ switch (id) {
+ case 1:
+ v2 = READ_LE_UINT32(card);
+ card += 4;
+ _vm->_controlPtr->_state->_vals[*card++] = v2;
+ break;
+
+ case 2:
+ v2 = _vm->_controlPtr->_state->_vals[*card++];
+ _vm->_controlPtr->_state->_vals[*card++] = v2;
+ break;
+
+ case 3:
+ v2 = READ_LE_UINT32(card);
+ card += 4;
+ _vm->_controlPtr->_state->_vals[*card++] = v2;
+ break;
+
+ case 4:
+ v2 = _vm->_controlPtr->_state->_vals[*card++];
+ _vm->_controlPtr->_state->_vals[*card++] = v2;
+ break;
+
+ case 5: {
+ v2 = READ_LE_UINT32(card);
+ card += 4;
+ int &v = _vm->_controlPtr->_state->_vals[*card++];
+ v -= v2;
+ break;
+ }
+
+ case 6: {
+ idx1 = *card++;
+ idx2 = *card++;
+
+ v2 = _vm->_controlPtr->_state->_vals[idx1];
+ int &v = _vm->_controlPtr->_state->_vals[idx2];
+ v -= v2;
+ break;
+ }
+
+ case 7: {
+ int v3 = *card++;
+ v2 = READ_LE_UINT32(card);
+ card += 4;
+ int &v = _vm->_controlPtr->_state->_vals[v3];
+ v *= v2;
+ break;
+ }
+
+ case 8: {
+ idx1 = *card++;
+ idx2 = *card++;
+
+ int &v1 = _vm->_controlPtr->_state->_vals[idx1];
+ int &v2 = _vm->_controlPtr->_state->_vals[idx2];
+ v1 *= v2;
+ break;
+ }
+
+ case 9: {
+ idx1 = *card++;
+ v2 = READ_LE_UINT32(card);
+ card += 4;
+
+ int &v = _vm->_controlPtr->_state->_vals[idx1];
+ v /= v2;
+ break;
+ }
+
+ case 10: {
+ idx1 = *card++;
+ idx2 = *card++;
+
+ int &v1 = _vm->_controlPtr->_state->_vals[idx1];
+ int &v2 = _vm->_controlPtr->_state->_vals[idx2];
+ v1 /= v2;
+ break;
+ }
+
+ case 11:
+ v2 = READ_LE_UINT32(card);
+ card += 4;
+ v2 = _vm->getRandomNumber(v2 - 1) + 1;
+ _vm->_controlPtr->_state->_vals[*card++] = v2;
+ break;
+
+ case 17:
+ _vm->_glGoState = READ_LE_UINT16(card);
+ card += 2;
+ _vm->_glGoStack = -1;
+ break;
+
+ case 18:
+ v2 = _vm->_controlPtr->_state->_vals[*card++];
+ _vm->_glGoState = getStateFromID(v2);
+ break;
+
+ case 19:
+ _vm->_glGoState = READ_LE_UINT32(card);
+ card += 4;
+ _vm->_glGoStack = READ_LE_UINT16(card);
+ card += 2;
+ break;
+
+ case 23:
+ case 24:
+ case 27:
+ case 28:
+ varD -= 3;
+ // Deliberate fall-through
+
+ case 21:
+ case 22:
+ case 25:
+ case 26:
+ bVal = card[varD];
+ if (bVal == 61) {
+ if (cardPerform2(card, id)) {
+ card += varD;
+ while (*card != 30 && *card != 29)
+ card = cardPerform(card);
+
+ if (*card == 29) {
+ int count = 1;
+ while (count > 0) {
+ card = getNextRecord(card);
+ if (*card == 30)
+ --count;
+ if (*card >= 21 && *card <= 28)
+ ++count;
+ }
+ }
+ } else {
+ card += varD;
+ int count = 1;
+ while (count > 0) {
+ card = getNextRecord(card);
+ if (*card == 29 || *card == 30)
+ --count;
+ if (*card < 21 || *card > 28)
+ continue;
+
+ const byte *nextP = getNextRecord(card + 2);
+ if (*nextP == 61)
+ ++count;
+ }
+ }
+
+ ++card;
+ } else {
+ if (cardPerform2(card, id)) {
+ card += varD;
+ card = cardPerform(card);
+ while (*card++ != 61) ;
+ } else {
+ card += varD;
+ while (*card != 61 && *card != 29)
+ ++card;
+ }
+ }
+ break;
+
+ case 41:
+ bVal = *card++;
+ assert(bVal < 8);
+ _fieldA[bVal] = READ_LE_UINT32(card);
+ card += 4;
+
+ _field2A[bVal] = READ_LE_UINT16(card);
+ card += 2;
+
+ case 45:
+ _newStateId = _nextStateId;
+ _newStackId = _stackId;
+ break;
+
+ case 46:
+ _vm->_glGoState = _newStateId;
+ _vm->_glGoStack = _newStackId;
+ _newStateId = -1;
+ _newStackId = -1;
+ break;
+
+ case 51:
+ setButtonFlag(READ_LE_UINT16(card), 64);
+ break;
+
+ case 52:
+ clearButtonFlag(READ_LE_UINT16(card), 64);
+ break;
+
+ default:
+ break;
+ }
+
+ return card;
+}
+
+bool ThreadResource::cardPerform2(const byte *pSrc, int cardCmdId) {
+ int vLong, vLong2;
+
+ switch (cardCmdId) {
+ case 21:
+ vLong = (int32)READ_LE_UINT32(pSrc + 1);
+ return _vm->_controlPtr->_state->_vals[*pSrc] == vLong;
+
+ case 22:
+ vLong = (int32)READ_LE_UINT32(pSrc + 1);
+ return _vm->_controlPtr->_state->_vals[*pSrc] != vLong;
+
+ case 23:
+ vLong = _vm->_controlPtr->_state->_vals[*pSrc];
+ vLong2 = _vm->_controlPtr->_state->_vals[*(pSrc + 1)];
+ return vLong == vLong2;
+
+ case 24:
+ vLong = _vm->_controlPtr->_state->_vals[*pSrc];
+ vLong2 = _vm->_controlPtr->_state->_vals[*(pSrc + 1)];
+ return vLong != vLong2;
+
+ case 25:
+ vLong = _vm->_controlPtr->_state->_vals[*pSrc];
+ vLong2 = (int32)READ_LE_UINT32(pSrc + 1);
+ return vLong < vLong2;
+
+ case 26:
+ vLong = _vm->_controlPtr->_state->_vals[*pSrc];
+ vLong2 = (int32)READ_LE_UINT32(pSrc + 1);
+ return vLong > vLong2;
+
+ case 27:
+ vLong = _vm->_controlPtr->_state->_vals[*pSrc];
+ vLong2 = _vm->_controlPtr->_state->_vals[*(pSrc + 1)];
+ return vLong < vLong2;
+
+ case 28:
+ vLong = _vm->_controlPtr->_state->_vals[*pSrc];
+ vLong2 = _vm->_controlPtr->_state->_vals[*(pSrc + 1)];
+ return vLong > vLong2;
+
+ default:
+ return false;
+ }
+}
+
+int ThreadResource::doApt() {
+ loadTheApt();
+
+ _vm->_currentVocId = 151;
+ _vm->_voy._viewBounds = _vm->_bVoy->boltEntry(_vm->_playStampGroupId)._rectResource;
+ Common::Array<RectEntry> &hotspots = _vm->_bVoy->boltEntry(
+ _vm->_playStampGroupId + 1)._rectResource->_entries;
+ _vm->_eventsManager.getMouseInfo();
+
+ // Very first time apartment is shown, start the phone message
+ if (_aptPos.x == -1) {
+ _aptPos.x = hotspots[2].left;
+ _aptPos.y = hotspots[2].top;
+ _vm->_currentVocId = 153;
+ }
+
+ if (_vm->_voy._playStampMode == 16) {
+ hotspots[0].left = 999;
+ hotspots[3].left = 999;
+ _aptPos.x = hotspots[4].left + 28;
+ _aptPos.y = hotspots[4].top + 28;
+ }
+
+ _vm->_eventsManager.setMousePos(Common::Point(_aptPos.x, _aptPos.y));
+ _vm->_soundManager.startVOCPlay(_vm->_soundManager.getVOCFileName(_vm->_currentVocId));
+ _vm->_currentVocId = 151;
+
+ _vm->_graphicsManager.setColor(129, 82, 82, 82);
+ _vm->_graphicsManager.setColor(130, 112, 112, 112);
+ _vm->_graphicsManager.setColor(131, 215, 215, 215);
+ _vm->_graphicsManager.setColor(132, 235, 235, 235);
+
+ _vm->_eventsManager._intPtr._hasPalette = true;
+
+ // Main loop to allow users to move the cursor and select hotspots
+ int hotspotId;
+ int prevHotspotId = -1;
+ Common::Point pt;
+ PictureResource *pic;
+ Common::Rect gmmHotspot(75, 125, 130, 140);
+
+ do {
+ _vm->_voyeurArea = AREA_APARTMENT;
+
+ if (_vm->_loadGameSlot != -1) {
+ // Load a savegame
+ _vm->loadGame(_vm->_loadGameSlot);
+ _vm->_loadGameSlot = -1;
+ }
+
+ _vm->_eventsManager.getMouseInfo();
+ if (!_vm->_soundManager.getVOCStatus()) {
+ // Previous sound ended, so start up a new one
+ _vm->_currentVocId = 151 - _vm->getRandomNumber(4);
+ _vm->_soundManager.startVOCPlay(_vm->_soundManager.getVOCFileName(_vm->_currentVocId));
+ }
+
+ // Loop through the hotspot list
+ hotspotId = -1;
+ pt = _vm->_eventsManager.getMousePos();
+ for (int idx = 0; idx < (int)hotspots.size(); ++idx) {
+ if (hotspots[idx].contains(pt)) {
+ // Cursor is within hotspot area
+
+ // Don't allow the camera to be highlighted on Monday morning.
+ if (idx == 0 && _vm->_voy._transitionId == 17)
+ continue;
+
+ // Set the highlighted hotspot Id
+ hotspotId = idx;
+
+ if (hotspotId != prevHotspotId) {
+ // Check for whether to replace hotspot Id for "Watch TV" for
+ // "Review the Tape" if player has already watched the TV
+ if ((_vm->_voy._eventFlags & EVTFLAG_100) && (hotspotId == 2))
+ hotspotId = 5;
+
+ // Draw the text description for the highlighted hotspot
+ pic = _vm->_bVoy->boltEntry(_vm->_playStampGroupId +
+ hotspotId + 6)._picResource;
+ _vm->_graphicsManager.sDrawPic(pic, *_vm->_graphicsManager._vPort,
+ Common::Point(106, 200));
+ }
+
+ break;
+ }
+ }
+
+ // Check for presence in ScummVM GMM
+ if (gmmHotspot.contains(pt))
+ hotspotId = 42;
+
+ // Draw either standard or highlighted eye cursor
+ pic = _vm->_bVoy->boltEntry((hotspotId == -1) ? _vm->_playStampGroupId + 2 :
+ _vm->_playStampGroupId + 3)._picResource;
+ _vm->_graphicsManager.sDrawPic(pic, *_vm->_graphicsManager._vPort, pt);
+
+ _vm->flipPageAndWait();
+
+ if (hotspotId == 42 && _vm->_eventsManager._leftClick) {
+ // Show the ScummVM GMM
+ _vm->_eventsManager.getMouseInfo();
+ _vm->openMainMenuDialog();
+ }
+
+ } while (!_vm->shouldQuit() && (!_vm->_eventsManager._leftClick || hotspotId == -1));
+
+ pt = _vm->_eventsManager.getMousePos();
+ _aptPos.x = pt.x;
+ _aptPos.y = pt.y;
+
+ switch (hotspotId) {
+ case 0:
+ _vm->_voy._aptLoadMode = 140;
+ break;
+ case 1:
+ _vm->_voy._aptLoadMode = 143;
+ break;
+ case 2:
+ _vm->_voy._aptLoadMode = 142;
+ case 5:
+ _vm->_voy._aptLoadMode = 141;
+ break;
+ default:
+ _vm->_voy._aptLoadMode = -1;
+ break;
+ }
+
+ freeTheApt();
+ if (_vm->_voy._transitionId == 1 && hotspotId == 0)
+ _vm->checkTransition();
+
+ if (!hotspotId)
+ _vm->makeViewFinder();
+
+ return hotspotId;
+}
+
+void ThreadResource::doRoom() {
+ VoyeurEngine &vm = *_vm;
+ SVoy &voy = vm._voy;
+
+ vm.makeViewFinderP();
+ voy._fadingType = 0;
+
+ if (!vm._bVoy->getBoltGroup(vm._playStampGroupId, true))
+ return;
+
+ vm._graphicsManager._backColors = vm._bVoy->boltEntry(vm._playStampGroupId + 1)._cMapResource;
+ vm._graphicsManager._backgroundPage = vm._bVoy->boltEntry(vm._playStampGroupId)._picResource;
+ (*vm._graphicsManager._vPort)->setupViewPort(vm._graphicsManager._backgroundPage);
+ vm._graphicsManager._backColors->startFade();
+
+ voy._fadingStep1 = 2;
+ voy._fadingStep2 = 0;
+ voy._fadingType = 1;
+
+ Common::Array<RectEntry> &hotspots = vm._bVoy->boltEntry(vm._playStampGroupId + 4)._rectResource->_entries;
+ int hotspotId = -1;
+
+ PictureResource *crosshairsCursor = vm._bVoy->boltEntry(vm._playStampGroupId + 2)._picResource;
+ PictureResource *magnifierCursor = vm._bVoy->boltEntry(vm._playStampGroupId + 3)._picResource;
+ vm._eventsManager.showCursor();
+
+ RectResource viewBounds(48, 38, 336, 202);
+ voy._viewBounds = &viewBounds;
+
+ vm._eventsManager.getMouseInfo();
+ vm._eventsManager.setMousePos(Common::Point(192, 120));
+ voy._fadingType = 0;
+ vm._currentVocId = 146;
+ voy._musicStartTime = voy._RTVNum;
+
+ voy._vocSecondsOffset = 0;
+ vm._soundManager.startVOCPlay(vm._currentVocId);
+ voy._eventFlags &= ~EVTFLAG_TIME_DISABLED;
+
+ bool breakFlag = false;
+ while (!vm.shouldQuit() && !breakFlag) {
+ _vm->_voyeurArea = AREA_ROOM;
+ vm._graphicsManager.setColor(128, 0, 255, 0);
+ vm._eventsManager._intPtr._hasPalette = true;
+
+ do {
+ if (vm._currentVocId != -1 && !vm._soundManager.getVOCStatus()) {
+ voy._musicStartTime = voy._RTVNum;
+ voy._vocSecondsOffset = 0;
+ vm._soundManager.startVOCPlay(vm._currentVocId);
+ }
+
+ vm._eventsManager.getMouseInfo();
+ Common::Point pt = vm._eventsManager.getMousePos();
+ pt += Common::Point(30, 15);
+
+ hotspotId = -1;
+ if (voy._computerTextId != -1 && voy._rect4E4.contains(pt))
+ hotspotId = 999;
+
+ for (uint idx = 0; idx < hotspots.size(); ++idx) {
+ if (hotspots[idx].contains(pt)) {
+ int arrIndex = hotspots[idx]._arrIndex;
+ if (voy._roomHotspotsEnabled[arrIndex - 1]) {
+ hotspotId = idx;
+ break;
+ }
+ }
+ }
+
+ if (hotspotId == -1) {
+ vm._eventsManager.setCursorColor(128, 0);
+ vm._eventsManager.setCursor(crosshairsCursor);
+ } else if (hotspotId != 999 || voy._RTVNum < voy._computerTimeMin ||
+ (voy._computerTimeMax - 2) < voy._RTVNum) {
+ vm._eventsManager.setCursorColor(128, 1);
+ vm._eventsManager.setCursor(magnifierCursor);
+ } else {
+ vm._eventsManager.setCursorColor(128, 2);
+ vm._eventsManager.setCursor(magnifierCursor);
+ }
+
+ vm._eventsManager._intPtr._hasPalette = true;
+ vm._graphicsManager.flipPage();
+ vm._eventsManager.sWaitFlip();
+ } while (!vm.shouldQuit() && !vm._eventsManager._mouseClicked);
+
+ if (!vm._eventsManager._leftClick || hotspotId == -1) {
+ if (vm._eventsManager._rightClick)
+ breakFlag = true;
+
+ Common::Point pt = vm._eventsManager.getMousePos();
+ vm._eventsManager.getMouseInfo();
+ vm._eventsManager.setMousePos(pt);
+ } else {
+ voy._eventFlags |= EVTFLAG_RECORDING;
+ vm._eventsManager.hideCursor();
+ vm._eventsManager.startCursorBlink();
+
+ if (hotspotId == 999) {
+ _vm->flipPageAndWait();
+
+ if (vm._currentVocId != -1) {
+ voy._vocSecondsOffset = voy._RTVNum - voy._musicStartTime;
+ vm._soundManager.stopVOCPlay();
+ }
+
+ vm.getComputerBrush();
+ _vm->flipPageAndWait();
+
+ vm._voy.addComputerEventStart();
+
+ vm._eventsManager._mouseClicked = false;
+ vm._eventsManager.startCursorBlink();
+
+ int totalChars = vm.doComputerText(9999);
+ if (totalChars)
+ vm._voy.addComputerEventEnd(totalChars);
+
+ vm._bVoy->freeBoltGroup(0x4900);
+ } else {
+ vm.doEvidDisplay(hotspotId, 999);
+ }
+
+ voy._eventFlags &= ~EVTFLAG_RECORDING;
+ if (!vm._eventsManager._mouseClicked)
+ vm._eventsManager.delayClick(18000);
+
+ // WORKAROUND: Skipped code from the original, that freed the group,
+ // reloaded it, and reloaded the cursors
+
+ vm._graphicsManager._backColors = vm._bVoy->boltEntry(
+ vm._playStampGroupId + 1)._cMapResource;
+ vm._graphicsManager._backgroundPage = vm._bVoy->boltEntry(
+ vm._playStampGroupId)._picResource;
+
+ (*vm._graphicsManager._vPort)->setupViewPort();
+ vm._graphicsManager._backColors->startFade();
+ _vm->flipPageAndWait();
+
+ while (!vm.shouldQuit() && (vm._eventsManager._fadeStatus & 1))
+ vm._eventsManager.delay(1);
+ vm._eventsManager.hideCursor();
+
+ while (!vm.shouldQuit() && voy._fadingAmount2 > 0) {
+ if (voy._fadingAmount1 < 63) {
+ voy._fadingAmount1 += 4;
+ if (voy._fadingAmount1 > 63)
+ voy._fadingAmount1 = 63;
+ }
+
+ if (voy._fadingAmount2 > 0) {
+ voy._fadingAmount2 -= 8;
+ if (voy._fadingAmount2 < 0)
+ voy._fadingAmount2 = 0;
+ }
+
+ vm._eventsManager.delay(1);
+ }
+
+ _vm->flipPageAndWait();
+
+ vm._graphicsManager.fadeUpICF1(0);
+ voy._eventFlags &= EVTFLAG_RECORDING;
+ vm._eventsManager.showCursor();
+ }
+ }
+
+ voy._eventFlags = EVTFLAG_TIME_DISABLED;
+ vm._eventsManager.incrementTime(1);
+ voy._viewBounds = nullptr;
+ voy._fadingType = 0;
+ vm.makeViewFinderP();
+
+ if (voy._boltGroupId2 != -1) {
+ vm._bVoy->freeBoltGroup(voy._boltGroupId2, 1);
+ voy._boltGroupId2 = -1;
+ }
+
+ if (vm._playStampGroupId != -1) {
+ vm._bVoy->freeBoltGroup(vm._playStampGroupId);
+ vm._playStampGroupId = -1;
+ }
+
+ if (vm._currentVocId != -1) {
+ vm._soundManager.stopVOCPlay();
+ vm._currentVocId = -1;
+ }
+
+ vm._eventsManager.hideCursor();
+ chooseSTAMPButton(0);
+}
+
+int ThreadResource::doInterface() {
+ PictureResource *pic;
+ Common::Point pt;
+
+ _vm->_voy._eventFlags |= EVTFLAG_TIME_DISABLED;
+ if (_vm->_voy._abortInterface) {
+ _vm->_voy._abortInterface = false;
+ return -2;
+ }
+
+ _vm->_voy._eventFlags &= ~EVTFLAG_100;
+ _vm->_playStampGroupId = -1;
+ _vm->_eventsManager._intPtr._flashStep = 1;
+ _vm->_eventsManager._intPtr._flashTimer = 0;
+
+ if (_vm->_voy._RTVNum >= _vm->_voy._RTVLimit || _vm->_voy._RTVNum < 0)
+ _vm->_voy._RTVNum = _vm->_voy._RTVLimit - 1;
+
+ if (_vm->_voy._transitionId < 15 && _vm->_debugger._isTimeActive &&
+ (_vm->_voy._RTVLimit - 3) < _vm->_voy._RTVNum) {
+ _vm->_voy._RTVNum = _vm->_voy._RTVLimit;
+ _vm->makeViewFinder();
+
+ _vm->initIFace();
+ _vm->_voy._RTVNum = _vm->_voy._RTVLimit - 4;
+ _vm->_voy._eventFlags &= ~EVTFLAG_TIME_DISABLED;
+
+ while (!_vm->shouldQuit() && _vm->_voy._RTVNum < _vm->_voy._RTVLimit) {
+ _vm->flashTimeBar();
+ _vm->_eventsManager.delayClick(1);
+ }
+
+ _vm->_voy._eventFlags |= EVTFLAG_TIME_DISABLED;
+ chooseSTAMPButton(20);
+ parsePlayCommands();
+ }
+
+ _vm->checkTransition();
+ _vm->makeViewFinder();
+ _vm->_eventsManager.getMouseInfo();
+ _vm->initIFace();
+
+ Common::Array<RectEntry> *hotspots = &_vm->_bVoy->boltEntry(
+ _vm->_playStampGroupId + 1)._rectResource->_entries;
+ _vm->_currentVocId = 151 - _vm->getRandomNumber(5);
+ _vm->_voy._vocSecondsOffset = _vm->getRandomNumber(29);
+
+ Common::String fname = _vm->_soundManager.getVOCFileName(_vm->_currentVocId);
+ _vm->_soundManager.startVOCPlay(fname);
+ _vm->_eventsManager.getMouseInfo();
+
+ _vm->_graphicsManager.setColor(240, 220, 220, 220);
+ _vm->_eventsManager._intPtr._hasPalette = true;
+ _vm->_voy._eventFlags &= ~EVTFLAG_TIME_DISABLED;
+
+ // Set the cusor
+ PictureResource *crosshairsCursor = _vm->_bVoy->boltEntry(0x112)._picResource;
+ PictureResource *eyeCursor = _vm->_bVoy->boltEntry(0x113)._picResource;
+ PictureResource *listenCursor = _vm->_bVoy->boltEntry(0x114)._picResource;
+ PictureResource *mangifyCursor = _vm->_bVoy->boltEntry(0x115)._picResource;
+
+ _vm->_eventsManager.setCursor(crosshairsCursor);
+
+ // Main loop
+ int regionIndex = 0;
+ Common::Rect mansionViewBounds(MANSION_VIEW_X, MANSION_VIEW_Y,
+ MANSION_VIEW_X + MANSION_VIEW_WIDTH, MANSION_VIEW_Y + MANSION_VIEW_HEIGHT);
+
+ do {
+ _vm->_voyeurArea = AREA_INTERFACE;
+ _vm->doTimeBar(true);
+ _vm->_eventsManager.getMouseInfo();
+
+ if (checkMansionScroll())
+ _vm->doScroll(_vm->_mansionViewPos);
+
+ _vm->checkPhoneCall();
+ if (!_vm->_soundManager.getVOCStatus()) {
+ _vm->_currentVocId = 151 - _vm->getRandomNumber(5);
+ _vm->_soundManager.startVOCPlay(_vm->_soundManager.getVOCFileName(_vm->_currentVocId));
+ }
+
+ // Calculate the mouse position within the entire mansion
+ pt = _vm->_eventsManager.getMousePos();
+ if (!mansionViewBounds.contains(pt))
+ pt = Common::Point(-1, -1);
+ else
+ pt = _vm->_mansionViewPos +
+ Common::Point(pt.x - MANSION_VIEW_X, pt.y - MANSION_VIEW_Y);
+ regionIndex = -1;
+
+ for (int hotspotIdx = 0; hotspotIdx < (int)hotspots->size(); ++hotspotIdx) {
+ if ((*hotspots)[hotspotIdx].contains(pt)) {
+ // Rect check done
+ for (int arrIndex = 0; arrIndex < 3; ++arrIndex) {
+ if (_vm->_voy._audioHotspotTimes.isInRange(arrIndex, hotspotIdx, _vm->_voy._RTVNum)) {
+ // Set the ear cursor for an audio event
+ _vm->_eventsManager.setCursor(listenCursor);
+ regionIndex = hotspotIdx;
+ }
+
+ if (_vm->_voy._evidenceHotspotTimes.isInRange(arrIndex, hotspotIdx, _vm->_voy._RTVNum)) {
+ // Set the magnifier cursor for an evidence event
+ _vm->_eventsManager.setCursor(mangifyCursor);
+ regionIndex = hotspotIdx;
+ }
+ }
+
+ for (int arrIndex = 0; arrIndex < 8; ++arrIndex) {
+ if (_vm->_voy._videoHotspotTimes.isInRange(arrIndex, hotspotIdx, _vm->_voy._RTVNum)) {
+ // Set the eye cursor for a video event
+ _vm->_eventsManager.setCursor(eyeCursor);
+ regionIndex = hotspotIdx;
+ }
+ }
+ }
+ }
+
+ if (regionIndex == -1) {
+ // Reset back to the crosshairs cursor
+ _vm->_eventsManager.setCursor(crosshairsCursor);
+ }
+
+ // Regularly update the time display
+ if (_vm->_voy._RTANum & 2) {
+ _vm->_graphicsManager.drawANumber(*_vm->_graphicsManager._vPort,
+ _vm->_gameMinute / 10, Common::Point(190, 25));
+ _vm->_graphicsManager.drawANumber(*_vm->_graphicsManager._vPort,
+ _vm->_gameMinute % 10, Common::Point(201, 25));
+
+ if (_vm->_voy._RTANum & 4) {
+ int v = _vm->_gameHour / 10;
+ _vm->_graphicsManager.drawANumber(*_vm->_graphicsManager._vPort,
+ v == 0 ? 10 : v, Common::Point(161, 25));
+ _vm->_graphicsManager.drawANumber(*_vm->_graphicsManager._vPort,
+ _vm->_gameHour % 10, Common::Point(172, 25));
+
+ pic = _vm->_bVoy->boltEntry(_vm->_voy._isAM ? 272 : 273)._picResource;
+ _vm->_graphicsManager.sDrawPic(pic, *_vm->_graphicsManager._vPort,
+ Common::Point(215, 27));
+ }
+ }
+
+ _vm->_voy._RTANum = 0;
+ _vm->flipPageAndWait();
+
+ pt = _vm->_eventsManager.getMousePos();
+ if ((_vm->_voy._RTVNum >= _vm->_voy._RTVLimit) || ((_vm->_voy._eventFlags & 0x80) &&
+ _vm->_eventsManager._rightClick && (pt.x == 0))) {
+ // Time to transition to the next time period
+ _vm->_eventsManager.getMouseInfo();
+
+ if (_vm->_voy._transitionId == 15) {
+ regionIndex = 20;
+ _vm->_voy._transitionId = 17;
+ _vm->_soundManager.stopVOCPlay();
+ _vm->checkTransition();
+ _vm->_eventsManager._leftClick = true;
+ } else {
+ _vm->_voy._eventFlags |= EVTFLAG_TIME_DISABLED;
+
+ chooseSTAMPButton(20);
+ parsePlayCommands();
+ _vm->checkTransition();
+ _vm->makeViewFinder();
+
+ _vm->initIFace();
+
+ hotspots = &_vm->_bVoy->boltEntry(_vm->_playStampGroupId + 1)._rectResource->_entries;
+ _vm->_eventsManager.getMouseInfo();
+
+ _vm->_voy._eventFlags &= ~EVTFLAG_TIME_DISABLED;
+ _vm->_eventsManager._intPtr._flashStep = 1;
+ _vm->_eventsManager._intPtr._flashTimer = 0;
+ }
+ }
+ } while (!_vm->_eventsManager._rightClick && !_vm->shouldQuit() &&
+ (!_vm->_eventsManager._leftClick || regionIndex == -1));
+
+ _vm->_eventsManager.hideCursor();
+ _vm->_voy._eventFlags |= EVTFLAG_TIME_DISABLED;
+ _vm->_bVoy->freeBoltGroup(_vm->_playStampGroupId);
+ if (_vm->_currentVocId != -1)
+ _vm->_soundManager.stopVOCPlay();
+
+ return !_vm->_eventsManager._rightClick ? regionIndex : -2;
+}
+
+bool ThreadResource::checkMansionScroll() {
+ Common::Point pt = _vm->_eventsManager.getMousePos() -
+ Common::Point(MANSION_VIEW_X, MANSION_VIEW_Y);
+ Common::Point &viewPos = _vm->_mansionViewPos;
+ bool result = false;
+
+ // Scroll mansion view if close to any of the mansion edges
+ if (pt.x >= 0 && pt.x < MANSION_SCROLL_AREA_X && viewPos.x > 0) {
+ viewPos.x = MAX(viewPos.x - MANSION_SCROLL_INC_X, 0);
+ result = true;
+ }
+ if (pt.x >= (MANSION_VIEW_WIDTH - MANSION_SCROLL_AREA_X) &&
+ pt.x < MANSION_VIEW_WIDTH && viewPos.x < MANSION_MAX_X) {
+ viewPos.x = MIN(viewPos.x + MANSION_SCROLL_INC_X, MANSION_MAX_X);
+ result = true;
+ }
+ if (pt.y >= 0 && pt.y < MANSION_SCROLL_AREA_Y && viewPos.y > 0) {
+ viewPos.y = MAX(viewPos.y - MANSION_SCROLL_INC_Y, 0);
+ result = true;
+ }
+ if (pt.y >= (MANSION_VIEW_HEIGHT - MANSION_SCROLL_AREA_Y) &&
+ pt.y < MANSION_VIEW_HEIGHT && viewPos.y < MANSION_MAX_Y) {
+ viewPos.y = MIN(viewPos.y + MANSION_SCROLL_INC_Y, MANSION_MAX_Y);
+ result = true;
+ }
+
+ // Return whether mansion view area has changed
+ return result;
+}
+
+bool ThreadResource::goToStateID(int stackId, int id) {
+ debugC(DEBUG_BASIC, kDebugScripts, "goToStateID - %d, %d", stackId, id);
+
+ // Save current stack
+ savePrevious();
+
+ if (_stackId == stackId || stackId == -1 || loadAStack(stackId)) {
+ // Now in the correct state
+ _stateId = getStateFromID(id);
+
+ if (_stateId != -1) {
+ return doState();
+ } else {
+ _stateId = _savedStateId;
+ _stackId = _savedStackId;
+ }
+ }
+
+ return false;
+}
+
+bool ThreadResource::goToState(int stackId, int stateId) {
+ debugC(DEBUG_BASIC, kDebugScripts, "goToState - %d, %d", stackId, stateId);
+
+ savePrevious();
+ if (stackId == -1 || loadAStack(stackId)) {
+ if (stateId != -1)
+ _stateId = stateId;
+
+ return doState();
+ } else {
+ return false;
+ }
+}
+
+void ThreadResource::savePrevious() {
+ if (_savedStateId == _stateId && _stackId == _savedStackId) {
+ _flags &= ~1;
+ } else {
+ _flags |= 1;
+ _savedStateId = _stateId;
+ _savedStackId = _stackId;
+ }
+}
+
+void ThreadResource::setButtonFlag(int idx, byte bits) {
+ _buttonFlags[idx] |= bits;
+}
+
+void ThreadResource::clearButtonFlag(int idx, byte bits) {
+ _buttonFlags[idx] &= ~bits;
+}
+
+void ThreadResource::loadTheApt() {
+ switch (_vm->_voy._transitionId) {
+ case 1:
+ case 2:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 17:
+ _vm->_playStampGroupId = 0x5700;
+ break;
+ case 3:
+ _vm->_playStampGroupId = 0x5800;
+ break;
+ case 4:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ case 16:
+ _vm->_playStampGroupId = 0x5900;
+ break;
+ default:
+ break;
+ }
+
+ if (_vm->_voy._aptLoadMode == 143)
+ _vm->_voy._aptLoadMode = -1;
+
+ if (_vm->_voy._aptLoadMode != -1) {
+ doAptAnim(1);
+ _vm->_bVoy->getBoltGroup(_vm->_playStampGroupId);
+ _vm->_voy._aptLoadMode = -1;
+ _vm->_graphicsManager._backgroundPage = _vm->_bVoy->boltEntry(
+ _vm->_playStampGroupId + 5)._picResource;
+ (*_vm->_graphicsManager._vPort)->setupViewPort(
+ _vm->_graphicsManager._backgroundPage);
+ } else {
+ _vm->_bVoy->getBoltGroup(_vm->_playStampGroupId);
+ _vm->_graphicsManager._backgroundPage = _vm->_bVoy->boltEntry(
+ _vm->_playStampGroupId + 5)._picResource;
+ (*_vm->_graphicsManager._vPort)->setupViewPort(
+ _vm->_graphicsManager._backgroundPage);
+ }
+
+ CMapResource *pal = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + 4)._cMapResource;
+ pal->_steps = 1;
+ pal->startFade();
+ _vm->flipPageAndWaitForFade();
+}
+
+void ThreadResource::freeTheApt() {
+ _vm->_graphicsManager.fadeDownICF1(5);
+ _vm->flipPageAndWaitForFade();
+
+ _vm->_graphicsManager.fadeUpICF1(0);
+
+ if (_vm->_currentVocId != -1) {
+ _vm->_soundManager.stopVOCPlay();
+ _vm->_currentVocId = -1;
+ }
+
+ if (_vm->_voy._aptLoadMode == -1) {
+ _vm->_graphicsManager.fadeDownICF(6);
+ } else {
+ doAptAnim(2);
+ }
+
+ if (_vm->_voy._aptLoadMode == 140) {
+ _vm->_graphicsManager.screenReset();
+ _vm->_graphicsManager.resetPalette();
+ }
+
+ (*_vm->_graphicsManager._vPort)->setupViewPort(nullptr);
+ _vm->_bVoy->freeBoltGroup(_vm->_playStampGroupId);
+ _vm->_playStampGroupId = -1;
+ _vm->_voy._viewBounds = nullptr;
+}
+
+void ThreadResource::doAptAnim(int mode) {
+ _vm->_bVoy->freeBoltGroup(0x100, true);
+
+ // Figure out the resource to use
+ int id = 0;
+ switch (_vm->_voy._aptLoadMode) {
+ case 140:
+ id = 0x5A00;
+ break;
+ case 141:
+ id = 0x6000;
+ break;
+ case 142:
+ id = 0x6600;
+ break;
+ case 143:
+ id = 0x6C00;
+ break;
+ case 144:
+ id = 0x6F00;
+ break;
+ default:
+ break;
+ }
+
+ int id2 = (id == 0x6C00 || id == 0x6F00) ? 1 : 2;
+ switch (_vm->_voy._transitionId) {
+ case 3:
+ id += id2 << 8;
+ break;
+ case 4:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ case 16:
+ id += id2 << 9;
+ break;
+ default:
+ break;
+ }
+
+ if (mode == 1)
+ id += 0x100;
+
+ // Do the display
+ if (_vm->_bVoy->getBoltGroup(id)) {
+ CMapResource *pal = _vm->_bVoy->boltEntry(id)._cMapResource;
+ pal->_steps = 1;
+
+ for (int idx = 0; (idx < 6) && !_vm->shouldQuit(); ++idx) {
+ PictureResource *pic = _vm->_bVoy->boltEntry(id + idx + 1)._picResource;
+ (*_vm->_graphicsManager._vPort)->setupViewPort(pic);
+ pal->startFade();
+
+ _vm->flipPageAndWait();
+ _vm->_eventsManager.delayClick(5);
+ }
+
+ _vm->_bVoy->freeBoltGroup(id);
+ }
+
+ _vm->_bVoy->getBoltGroup(0x100);
+}
+
+void ThreadResource::synchronize(Common::Serializer &s) {
+ s.syncAsSint16LE(_aptPos.x);
+ s.syncAsSint16LE(_aptPos.y);
+
+ int stateId = _stateId;
+ int stackId = _stackId;
+ s.syncAsSint16LE(stateId);
+ s.syncAsSint16LE(stackId);
+
+ if (s.isLoading() && (stateId != _stateId || stackId != _stackId))
+ goToState(stackId, stateId);
+
+ s.syncAsSint16LE(_savedStateId);
+ s.syncAsSint16LE(_savedStackId);
+}
+
+} // End of namespace Voyeur
diff --git a/engines/voyeur/graphics.cpp b/engines/voyeur/graphics.cpp
new file mode 100644
index 0000000000..7ed0591871
--- /dev/null
+++ b/engines/voyeur/graphics.cpp
@@ -0,0 +1,1065 @@
+/* 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 "voyeur/graphics.h"
+#include "voyeur/voyeur.h"
+#include "voyeur/staticres.h"
+#include "engines/util.h"
+#include "graphics/palette.h"
+#include "graphics/surface.h"
+
+namespace Voyeur {
+
+/*------------------------------------------------------------------------*/
+
+DrawInfo::DrawInfo(int penColor, const Common::Point &pos, int flags) {
+ _penColor = penColor;
+ _pos = pos;
+ _flags = flags;
+}
+
+/*------------------------------------------------------------------------*/
+
+GraphicsManager::GraphicsManager(): _defaultDrawInfo(1, Common::Point(), 0), _drawPtr(&_defaultDrawInfo) {
+ _SVGAPage = 0;
+ _SVGAMode = 0;
+ _SVGAReset = 0;
+ _screenOffset = 0;
+ _planeSelect = 0;
+ _sImageShift = 3;
+ _palFlag = false;
+ _MCGAMode = false;
+ _saveBack = true;
+ _drawTextPermFlag = false;
+ _clipPtr = NULL;
+ _viewPortListPtr = NULL;
+ _backgroundPage = NULL;
+ _vPort = NULL;
+ _fontPtr = NULL;
+ Common::fill(&_VGAColors[0], &_VGAColors[PALETTE_SIZE], 0);
+ _fontChar = new PictureResource(0, 0xff, 0xff, 0, 0, Common::Rect(), 0, NULL, 0);
+}
+
+void GraphicsManager::sInitGraphics() {
+ initGraphics(SCREEN_WIDTH, SCREEN_HEIGHT, false);
+ _screenSurface.create(SCREEN_WIDTH, SCREEN_HEIGHT, Graphics::PixelFormat::createFormatCLUT8());
+ clearPalette();
+}
+
+GraphicsManager::~GraphicsManager() {
+ _screenSurface.free();
+ delete _fontChar;
+}
+
+void GraphicsManager::setupMCGASaveRect(ViewPortResource *viewPort) {
+ _MCGAMode = true;
+
+ if (viewPort->_activePage) {
+ viewPort->_activePage->_flags |= 1;
+ Common::Rect *clipRect = _clipPtr;
+ _clipPtr = &viewPort->_clipRect;
+
+ sDrawPic(viewPort->_activePage, viewPort->_currentPic, Common::Point());
+
+ _clipPtr = clipRect;
+ }
+
+ viewPort->_rectListCount[1] = -1;
+}
+
+void GraphicsManager::addRectOptSaveRect(ViewPortResource *viewPort, int idx, const Common::Rect &bounds) {
+ if (viewPort->_rectListCount[idx] == -1)
+ return;
+
+ // TODO: Lots of code in original, which I suspect may be overlapping rect merging
+ viewPort->_rectListPtr[idx]->push_back(bounds);
+ ++viewPort->_rectListCount[idx];
+}
+
+void GraphicsManager::restoreMCGASaveRect(ViewPortResource *viewPort) {
+ if (viewPort->_rectListCount[0] != -1) {
+ for (int i = 0; i < viewPort->_rectListCount[0]; ++i) {
+ addRectOptSaveRect(viewPort, 1, (*viewPort->_rectListPtr[0])[i]);
+ }
+ } else {
+ viewPort->_rectListCount[1] = -1;
+ }
+
+ restoreBack(*viewPort->_rectListPtr[1], viewPort->_rectListCount[1], viewPort->_pages[0],
+ viewPort->_pages[1]);
+
+ int count = viewPort->_rectListCount[0];
+ restoreBack(*viewPort->_rectListPtr[0], viewPort->_rectListCount[0],
+ viewPort->_activePage, viewPort->_currentPic);
+
+ SWAP(viewPort->_rectListPtr[0], viewPort->_rectListPtr[1]);
+ viewPort->_rectListCount[1] = count;
+}
+
+void GraphicsManager::addRectNoSaveBack(ViewPortResource *viewPort, int idx, const Common::Rect &bounds) {
+ // Stubbed/dummy method in the original.
+}
+
+void GraphicsManager::sDrawPic(DisplayResource *srcDisplay, DisplayResource *destDisplay,
+ const Common::Point &initialOffset) {
+ int var4C = 0;
+ int width1, width2;
+ int widthDiff, widthDiff2;
+ int height1;
+ int srcOffset;
+ int screenOffset;
+ int srcFlags, destFlags;
+ ViewPortResource *destViewPort = NULL;
+ Common::Rect newBounds;
+ Common::Rect backBounds;
+ int var22 = 0;
+ int var24 = 0;
+ bool isClipped = false;
+ int var26;
+ byte pixel = 0;
+ int runLength;
+
+ byte *srcImgData, *destImgData;
+ byte *srcP, *destP;
+ byte byteVal, byteVal2;
+
+ // Get the picture parameters, or deference viewport pointers to get their pictures
+ PictureResource *srcPic = (PictureResource *)srcDisplay;
+ PictureResource *destPic = (PictureResource *)destDisplay;
+
+ if (srcDisplay->_flags & DISPFLAG_VIEWPORT) {
+ // A viewport was passed, not a picture
+ srcPic = ((ViewPortResource *)srcDisplay)->_currentPic;
+ }
+ if (destDisplay->_flags & DISPFLAG_VIEWPORT) {
+ destViewPort = (ViewPortResource *)destDisplay;
+ destPic = destViewPort->_currentPic;
+ }
+
+ Common::Point offset = Common::Point(initialOffset.x + srcPic->_bounds.left - destPic->_bounds.left,
+ initialOffset.y + srcPic->_bounds.top - destPic->_bounds.top);
+ width1 = width2 = srcPic->_bounds.width();
+ height1 = srcPic->_bounds.height();
+ srcOffset = 0;
+ srcFlags = srcPic->_flags;
+ destFlags = destPic->_flags;
+ byte *cursorData = NULL;
+
+ if (srcFlags & 1) {
+ if (_clipPtr) {
+ int xs = _clipPtr->left - destPic->_bounds.left;
+ int ys = _clipPtr->top - destPic->_bounds.top;
+ newBounds = Common::Rect(xs, ys, xs + _clipPtr->width(), ys + _clipPtr->height());
+ } else if (destViewPort) {
+ int xs = destViewPort->_clipRect.left - destPic->_bounds.left;
+ int ys = destViewPort->_clipRect.top - destPic->_bounds.top;
+ newBounds = Common::Rect(xs, ys, xs + destViewPort->_clipRect.width(),
+ ys + destViewPort->_clipRect.height());
+ } else {
+ newBounds = Common::Rect(0, 0, destPic->_bounds.width(), destPic->_bounds.height());
+ }
+
+ var24 = offset.y - newBounds.top;
+ if (var24 < 0) {
+ srcOffset -= var24 * width2;
+ height1 += var24;
+ offset.y = newBounds.top;
+
+ if (height1 <= 0)
+ return;
+
+ isClipped = true;
+ }
+
+ int var20 = newBounds.bottom - (offset.y + height1);
+ if (var20 < 0) {
+ height1 += var20;
+ if (height1 <= 0)
+ return;
+ }
+
+ var22 = offset.x - newBounds.left;
+ if (var22 < 0) {
+ srcOffset -= var22;
+ width2 += var22;
+ offset.x = newBounds.left;
+
+ if (width2 <= 0)
+ return;
+
+ isClipped = true;
+ }
+
+ var26 = newBounds.right - (offset.x + width2);
+ if (var26 < 0) {
+ width2 += var26;
+ if (width2 <= 0)
+ return;
+
+ isClipped = true;
+ }
+ }
+
+ screenOffset = offset.y * destPic->_bounds.width() + offset.x;
+ widthDiff = width1 - width2;
+ widthDiff2 = destPic->_bounds.width() - width2;
+
+ if (destViewPort) {
+ if (!_saveBack || ((srcPic->_flags & DISPFLAG_800) != 0)) {
+ backBounds.left = destPic->_bounds.left + offset.x;
+ backBounds.top = destPic->_bounds.top + offset.y;
+ backBounds.setWidth(width2);
+ backBounds.setHeight(height1);
+ addRectOptSaveRect(destViewPort, 1, backBounds);
+
+ } else if (!destViewPort->_addFn) {
+ if (destViewPort->_rectListCount[destViewPort->_pageIndex] < -1) {
+ Common::Rect r;
+ r.left = destPic->_bounds.left + offset.x;
+ r.top = destPic->_bounds.top + offset.y;
+ r.setWidth(width2);
+ r.setHeight(height1);
+
+ (*destViewPort->_rectListPtr[destViewPort->_pageIndex]).push_back(r);
+ ++destViewPort->_rectListCount[destViewPort->_pageIndex];
+ }
+ } else {
+ int xs = offset.x + destPic->_bounds.left;
+ int ys = offset.y + destPic->_bounds.top;
+ backBounds = Common::Rect(xs, ys, xs + width2, ys + height1);
+
+ (this->*destViewPort->_addFn)(destViewPort, destViewPort->_bounds.top, backBounds);
+ }
+ }
+
+ if (srcFlags & DISPFLAG_1000) {
+ srcImgData = srcPic->_imgData + (var4C << 14) + _screenOffset;
+ for (uint idx = 0; idx < srcPic->_maskData; ++idx) {
+ if (var4C < 4) {
+ EMSMapPageHandle(srcPic->_planeSize, srcPic->_imgData[idx], var4C);
+ ++var4C;
+ }
+ }
+ } else {
+ srcImgData = srcPic->_imgData;
+ }
+ if (destFlags & DISPFLAG_1000) {
+ destImgData = destPic->_imgData + (var4C << 14) + _screenOffset;
+ for (uint idx = 0; idx < srcPic->_maskData; ++idx) {
+ if (var4C < 4) {
+ EMSMapPageHandle(destPic->_planeSize, destPic->_imgData[idx], var4C);
+ ++var4C;
+ }
+ }
+ } else {
+ destImgData = destPic->_imgData;
+ }
+
+ _SVGAPage = _SVGAReset;
+ if (srcPic->_select != 0xff)
+ return;
+
+ if (destFlags & DISPFLAG_CURSOR) {
+ cursorData = new byte[width2 * height1];
+ Common::fill(cursorData, cursorData + width2 * height1, 0);
+ destImgData = cursorData;
+ }
+
+ if (srcPic->_pick == 0xff) {
+ if (srcFlags & DISPFLAG_8) {
+ error("TODO: sDrawPic variation");
+ } else {
+ // loc_258B8
+ srcP = srcImgData + srcOffset;
+
+ if (destFlags & DISPFLAG_8) {
+ // loc_258D8
+ destP = destImgData + screenOffset;
+
+ if (srcFlags & DISPFLAG_2) {
+ // loc_25652
+ srcP = srcImgData + srcOffset;
+
+ if (destFlags & DISPFLAG_8) {
+ // loc_2566F
+ if (srcFlags & DISPFLAG_2) {
+ // loc_256FA
+ srcP = (byte *)_screenSurface.getPixels() + srcOffset;
+
+ for (int yp = 0; yp < height1; ++yp) {
+ for (int xp = 0; xp < width2; ++width2, ++srcP, ++destP) {
+ pixel = *srcP;
+ if (pixel)
+ *destP = pixel;
+ }
+
+ srcP += widthDiff;
+ destP += widthDiff2;
+ }
+ } else {
+ // loc_25706
+ for (int yp = 0; yp < height1; ++yp) {
+ Common::copy(srcP, srcP + width2, destP);
+ srcP += width2 + widthDiff;
+ destP += width2 + widthDiff2;
+ }
+ }
+ } else {
+ // loc_25773
+ destP = destImgData + screenOffset;
+
+ if (srcFlags & DISPFLAG_2) {
+ // loc_25793
+ for (int yp = 0; yp < height1; ++yp) {
+ Common::copy(srcP, srcP + width2, destP);
+ srcP += width2 + widthDiff;
+ destP += width2 + widthDiff2;
+ }
+ } else {
+ // loc_25829
+ destP = (byte *)_screenSurface.getPixels() + screenOffset;
+
+ for (int yp = 0; yp < height1; ++yp) {
+ Common::copy(srcP, srcP + width2, destP);
+ srcP += width2 + widthDiff;
+ destP += width2 + widthDiff2;
+ }
+ }
+ }
+ } else {
+ // loc_25D40
+ if (srcFlags & DISPFLAG_100) {
+ // loc_25D4A
+ error("TODO: sDrawPic variation");
+ } else {
+ // loc_2606D
+ destP = (byte *)_screenSurface.getPixels() + screenOffset;
+
+ for (int yp = 0; yp < height1; ++yp) {
+ Common::copy(srcP, srcP + width2, destP);
+ destP += width2 + widthDiff2;
+ srcP += width2 + widthDiff;
+ }
+ }
+ }
+ } else {
+ // loc_2615E
+ destP = destImgData + screenOffset;
+
+ if (srcFlags & DISPFLAG_2) {
+ // loc_2617e
+ if (srcFlags & DISPFLAG_100) {
+ // loc_26188
+ srcP = srcImgData;
+ if (isClipped) {
+ // loc_26199
+ var22 = (var22 < 0) ? -var22 : 0;
+ var26 = var22 + width2;
+ var24 = (var24 < 0) ? -var24 : 0;
+
+ width2 = srcPic->_bounds.width();
+ height1 = var24 + height1;
+
+ for (int yp = 0; yp < height1; ++yp) {
+ runLength = 0;
+
+ for (int xp = 0; xp < width2; ++xp, --runLength) {
+ if (runLength <= 0) {
+ pixel = *srcP++;
+ if (pixel & 0x80) {
+ pixel &= 0x7f;
+ runLength = *srcP++;
+ if (!runLength)
+ runLength = width2;
+ }
+ }
+
+ if (yp >= var24 && xp >= var22 && xp < var26) {
+ if (pixel > 0)
+ *destP = pixel;
+ ++destP;
+ }
+ }
+
+ if (yp >= var24)
+ destP += widthDiff2;
+ }
+ } else {
+ // loc_262BE
+ byteVal = 0;
+ for (int yp = 0; yp < height1; ++yp) {
+ for (int xp = 0; xp < width2; ++xp) {
+ byteVal2 = 0;
+ if (!byteVal2) {
+ byteVal = *++srcP;
+ if (byteVal & 0x80) {
+ byteVal &= 0x7f;
+ byteVal2 = *srcP++;
+
+ if (!byteVal2)
+ byteVal2 = width2;
+ }
+ }
+
+ if (byteVal > 0)
+ *destP = byteVal;
+
+ ++destP;
+ --byteVal2;
+ }
+
+ destP += widthDiff2;
+ }
+ }
+ } else {
+ // loc_2637F
+ // Copy with transparency
+ for (int yp = 0; yp < height1; ++yp) {
+ for (int xp = 0; xp < width2; ++xp, ++srcP, ++destP) {
+ if (*srcP != 0)
+ *destP = *srcP;
+ }
+
+ destP += widthDiff2;
+ srcP += widthDiff;
+ }
+ }
+ } else {
+ if (srcFlags & 0x100) {
+ // Simple run-length encoded image
+ srcP = srcImgData;
+
+ if (isClipped) {
+ // loc_26424
+ var22 = (var22 < 0) ? -var22 : 0;
+ var26 = var22 + width2;
+ var24 = (var24 < 0) ? -var24 : 0;
+ width2 = srcPic->_bounds.width();
+ height1 = var24 + height1;
+
+ for (int yp = 0; yp < height1; ++yp) {
+ runLength = 0;
+ for (int xp = 0; xp < width2; ++xp, --runLength) {
+ if (runLength <= 0) {
+ pixel = *srcP++;
+ if (pixel & 0x80) {
+ pixel &= 0x7F;
+ runLength = *srcP++;
+
+ if (!runLength)
+ runLength = width2;
+ }
+ }
+
+ if (yp >= var24 && xp >= var22 && xp < var26) {
+ *destP++ = pixel;
+ }
+ }
+
+ if (yp >= var24)
+ destP += widthDiff2;
+ }
+ } else {
+ // loc_26543
+ for (int yp = 0; yp < height1; ++yp) {
+ int runLength = 0;
+ for (int xp = 0; xp < width2; ++xp, --runLength) {
+ if (runLength <= 0) {
+ // Start of run length, so get pixel and repeat length
+ pixel = *srcP++;
+ if (pixel & 0x80) {
+ pixel &= 0x7f;
+ runLength = *srcP++;
+ if (runLength == 0)
+ runLength = width2;
+ }
+ }
+
+ // Copy pixel to output
+ *destP++ = pixel;
+ }
+
+ destP += widthDiff2;
+ }
+ }
+ } else {
+ for (int yp = 0; yp < height1; ++yp) {
+ Common::copy(srcP, srcP + width2, destP);
+ destP += width2 + widthDiff2;
+ srcP += width2 + widthDiff;
+ }
+ }
+ }
+ }
+ }
+ } else {
+ // loc_26666
+ if (srcPic->_pick == 0) {
+ // loc_2727A
+ byte onOff = srcPic->_onOff;
+
+ if (srcFlags & DISPFLAG_2) {
+ if (!(srcFlags & DISPFLAG_8)) {
+ srcP = srcImgData + srcOffset;
+
+ if (destFlags & DISPFLAG_8) {
+ // loc_272C3
+ error("TODO: sDrawPic variation");
+ } else {
+ destP = destImgData + screenOffset;
+ for (int yp = 0; yp < height1; ++yp) {
+ for (int xp = 0; xp < width2; ++xp, ++destP) {
+ if ((int8)*srcP++ < 0)
+ *destP = onOff;
+ }
+
+ destP += widthDiff2;
+ srcP += widthDiff;
+ }
+ }
+ }
+ } else {
+ // loc_27477
+ if (destFlags & DISPFLAG_8) {
+ // loc_27481
+ destP = (byte *)_screenSurface.getPixels() + screenOffset;
+ for (int yp = 0; yp < height1; ++yp) {
+ Common::fill(destP, destP + width2, onOff);
+ destP += width2 + widthDiff2;
+ }
+ } else {
+ // loc_2753C
+ destP = destImgData + screenOffset;
+
+ for (int yp = 0; yp < height1; ++yp) {
+ Common::fill(destP, destP + width2, onOff);
+ destP += width2 + widthDiff2;
+ }
+ }
+ }
+
+ } else {
+ // loc_26673
+ int pick = srcPic->_pick;
+ int onOff = srcPic->_onOff;
+
+ if (!(srcFlags & PICFLAG_PIC_OFFSET)) {
+ srcP = srcImgData += srcOffset;
+ int pixel = 0;
+
+ if (destFlags & PICFLAG_PIC_OFFSET) {
+ destP = destImgData + screenOffset;
+ if (srcFlags & PICFLAG_2) {
+ if (srcFlags & PICFLAG_100) {
+ if (isClipped) {
+ // loc_266E3
+ destP = (byte *)_screenSurface.getPixels() + screenOffset;
+ var22 = (var22 < 0) ? -var22 : 0;
+ var26 = var22 + width2;
+ var24 = (var24 < 0) ? -var24 : 0;
+ pick = 0x7F;
+ width2 = srcPic->_bounds.width();
+ height1 = var24 + height1;
+
+ for (int yp = 0; yp < height1; ++yp) {
+ int runLength = 0;
+ for (int xp = 0; xp < width2; ++xp, --runLength) {
+ if (runLength <= 0) {
+ pixel = *srcP++;
+ if (pixel & 0x80) {
+ pixel &= 0x7F;
+ runLength = *srcP++;
+ if (!runLength)
+ runLength = width2;
+ }
+ }
+
+ if (yp >= var24 && xp >= var22 && xp < var26) {
+ if (pixel) {
+ *destP = (pixel & pick) ^ onOff;
+ }
+ ++destP;
+ }
+ }
+ if (yp >= var24)
+ destP += widthDiff2;
+ }
+ } else {
+ // loc_26815
+ destP = (byte *)_screenSurface.getPixels() + screenOffset;
+
+ for (int yp = 0; yp < height1; ++yp) {
+ for (int xp = 0; xp < width2; ++xp, ++destP) {
+ byteVal2 = 0;
+ for (int xp = 0; xp < width2; ++xp, ++destP, --byteVal2) {
+ if (!byteVal2) {
+ pixel = *srcP++;
+ if (pixel & 0x80) {
+ pixel &= 0x7F;
+ byteVal2 = *srcP++;
+ if (!byteVal2) {
+ byteVal2 = width2;
+ }
+ }
+ }
+
+ if (pixel)
+ *destP = (pixel & pick) ^ onOff;
+ }
+ }
+
+ destP += widthDiff2;
+ }
+ }
+ } else {
+ // Direct screen write
+ destP = (byte *)_screenSurface.getPixels() + screenOffset;
+
+ for (int yp = 0; yp < height1; ++yp) {
+ for (int xp = 0; xp < width2; ++xp, ++srcP, ++destP) {
+ if (*srcP)
+ *destP = (*srcP & pick) ^ onOff;
+ }
+ destP += widthDiff2;
+ srcP += widthDiff;
+ }
+ }
+ } else if (srcFlags & PICFLAG_100) {
+ srcP = srcImgData;
+ if (isClipped) {
+ // loc_269FD
+ var22 = (var22 < 0) ? -var22 : 0;
+ var26 = var22 + width2;
+ var24 = (var24 < 0) ? -var24 : 0;
+ width2 = srcPic->_bounds.width();
+ height1 = var24 + height1;
+
+ for (int yp = 0; yp < height1; ++yp) {
+ runLength = 0;
+ for (int xp = 0; xp < width2; ++xp, --runLength) {
+ if (runLength <= 0) {
+ pixel = *srcP++;
+ if (pixel & 0x80) {
+ pixel &= 0x7F;
+ runLength = *srcP++;
+
+ if (!runLength)
+ runLength = width2;
+ }
+ }
+
+ if (yp >= var24 && xp >= var22 && xp < var26) {
+ *destP++ = (pixel & 0x80) ^ onOff;
+ }
+ }
+ }
+ } else {
+ // loc_26BD5
+ destP = (byte *)_screenSurface.getPixels() + screenOffset;
+
+ for (int yp = 0; yp < height1; ++yp) {
+ byteVal2 = 0;
+
+ for (int xp = 0; xp < width2; ++xp, ++destP) {
+ if (!byteVal2) {
+ pixel = *srcP++;
+ if (pixel & 0x80) {
+ pixel &= 0x7F;
+ byteVal2 = *srcP++;
+ if (!byteVal2)
+ byteVal2 = width2;
+ }
+ }
+
+ *destP = (pixel & pick) ^ onOff;
+ }
+
+ destP += widthDiff2;
+ }
+ }
+ } else {
+ // loc_26C9A
+ destP = (byte *)_screenSurface.getPixels() + screenOffset;
+
+ for (int yp = 0; yp < height1; ++yp) {
+ for (int xp = 0; xp < width2; ++xp, ++srcP, ++destP) {
+ *destP = (*srcP & pick) ^ onOff;
+ }
+ destP += widthDiff2;
+ srcP += widthDiff;
+ }
+ }
+ } else {
+ // loc_26D2F
+ destP = destImgData + screenOffset;
+
+ if (srcFlags & PICFLAG_2) {
+ // loc_26D4F
+ if (srcFlags & PICFLAG_100) {
+ srcP = srcImgData;
+
+ if (isClipped) {
+ // loc_26D6A
+ var22 = (var22 < 0) ? -var22 : 0;
+ var26 = var22 + width2;
+ var24 = (var24 < 0) ? -var24 : 0;
+ width2 = srcPic->_bounds.width();
+ height1 = var24 + height1;
+
+ for (int yp = 0; yp < height1; ++yp) {
+ runLength = 0;
+
+ for (int xp = 0; xp < width2; ++xp, --runLength) {
+ if (runLength <= 0) {
+ pixel = *srcP++;
+ if (pixel & 0x80) {
+ pixel &= 0x7F;
+ runLength = *srcP++;
+ if (!runLength)
+ runLength = width2;
+ }
+ }
+
+ if (yp >= var24 && xp >= var22 && xp < var26) {
+ if (pixel)
+ *destP = (pixel & pick) ^ onOff;
+
+ ++destP;
+ }
+ }
+
+ if (yp >= var24)
+ destP += widthDiff2;
+ }
+ } else {
+ // loc_26E95
+ for (int yp = 0; yp < height1; ++yp) {
+ byteVal2 = 0;
+ for (int xp = 0; xp < width2; ++xp, ++destP, --byteVal2) {
+ if (!byteVal2) {
+ pixel = *srcP++;
+ if (pixel & 0x80) {
+ pixel &= 0x7F;
+ byteVal2 = *srcP++;
+ if (!byteVal2)
+ byteVal2 = width2;
+ }
+ }
+
+ if (pixel)
+ *destP = (pixel & pick) ^ onOff;
+ }
+
+ destP += widthDiff2;
+ }
+ }
+ } else {
+ // loc_26F5D
+ for (int yp = 0; yp < height1; ++yp) {
+ for (int xp = 0; xp < width2; ++xp, ++srcP, ++destP) {
+ if (*srcP)
+ *destP = (*srcP & pick) ^ onOff;
+ }
+ destP += widthDiff2;
+ srcP += widthDiff;
+ }
+ }
+ } else {
+ // loc_26FEF
+ if (srcFlags & PICFLAG_100) {
+ // loc_26FF9
+ for (int yp = 0; yp < height1; ++yp) {
+ for (int xp = 0; xp < width2; ++xp, ++srcP, ++destP) {
+ *destP = (*srcP & pick) ^ onOff;
+ }
+ destP += widthDiff2;
+ srcP += widthDiff;
+ }
+ } else {
+ // loc_271F0
+ srcP = srcImgData;
+
+ if (isClipped) {
+ // loc_2700A
+ var22 = (var22 < 0) ? -var22 : 0;
+ var26 = var22 + width2;
+ var24 = (var24 < 0) ? -var24 : 0;
+ width2 = srcPic->_bounds.width();
+ height1 = var24 + height1;
+
+ for (int yp = 0; yp < height1; ++yp) {
+ runLength = 0;
+
+ for (int xp = 0; xp < width2; ++xp, --runLength) {
+ if (runLength <= 0) {
+ pixel = *srcP++;
+ if (pixel & 0x80) {
+ pixel &= 0x7F;
+ runLength = *srcP++;
+ if (!runLength)
+ runLength = width2;
+ }
+ }
+
+ if (yp >= var24 && xp >= var22 && xp < var26) {
+ *destP++ = (pixel & pick) ^ onOff;
+ }
+ }
+
+ if (yp >= var24)
+ destP += widthDiff2;
+ }
+ } else {
+ // loc_2712F
+ for (int yp = 0; yp < height1; ++yp) {
+ byteVal2 = 0;
+ for (int xp = 0; xp < width2; ++xp, ++destP, --byteVal2) {
+ if (!byteVal2) {
+ pixel = *srcP++;
+ if (pixel & 0x80) {
+ pixel &= 0x7F;
+ byteVal2 = *srcP++;
+ if (!byteVal2)
+ byteVal2 = width2;
+ }
+ }
+
+ *destP = (*srcP & pick) ^ onOff;
+ }
+ destP += widthDiff2;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (cursorData) {
+ _vm->_eventsManager.setCursor(cursorData, width2, height1);
+ delete[] cursorData;
+ }
+}
+
+void GraphicsManager::drawANumber(DisplayResource *display, int num, const Common::Point &pt) {
+ PictureResource *pic = _vm->_bVoy->boltEntry(num + 261)._picResource;
+ sDrawPic(pic, display, pt);
+}
+
+void GraphicsManager::fillPic(DisplayResource *display, byte onOff) {
+ PictureResource *pic;
+ if (display->_flags & DISPFLAG_VIEWPORT) {
+ pic = ((ViewPortResource *)display)->_currentPic;
+ } else {
+ pic = (PictureResource *)display;
+ }
+
+ PictureResource picResource;
+ picResource._flags = 0;
+ picResource._select = 0xff;
+ picResource._pick = 0;
+ picResource._onOff = onOff;
+ picResource._bounds = pic->_bounds;
+
+ sDrawPic(&picResource, display, Common::Point());
+}
+
+/**
+ * Queues the given picture for display
+ */
+void GraphicsManager::sDisplayPic(PictureResource *pic) {
+ _vm->_eventsManager._intPtr._flipWait = true;
+}
+
+void GraphicsManager::flipPage() {
+ Common::Array<ViewPortResource *> &viewPorts = _viewPortListPtr->_entries;
+ bool flipFlag = false;
+
+ for (uint idx = 0; idx < viewPorts.size(); ++idx) {
+ if (viewPorts[idx]->_flags & DISPFLAG_20) {
+ if ((viewPorts[idx]->_flags & (DISPFLAG_8 || DISPFLAG_1))
+ == (DISPFLAG_8 || DISPFLAG_1)) {
+ if (_planeSelect == idx)
+ sDisplayPic(viewPorts[idx]->_currentPic);
+ flipFlag = true;
+ }
+ }
+
+ if (flipFlag) {
+ ViewPortResource &viewPort = *viewPorts[idx];
+
+ viewPort._lastPage = viewPort._pageIndex;
+ ++viewPort._pageIndex;
+
+ if (viewPort._pageIndex >= viewPort._pageCount)
+ viewPort._pageIndex = 0;
+
+ assert(viewPort._pageIndex < 2);
+ viewPort._currentPic = viewPort._pages[viewPort._pageIndex];
+ viewPort._flags = (viewPort._flags & ~DISPFLAG_8) | DISPFLAG_40;
+ }
+ }
+}
+
+void GraphicsManager::restoreBack(Common::Array<Common::Rect> &rectList, int rectListCount,
+ PictureResource *srcPic, PictureResource *destPic) {
+ // WORKAROUND: Since _backgroundPage can point to a resource freed at the end of display methods,
+ // I'm now explicitly resetting it to null in screenReset(), so at this point it can be null
+ if (!srcPic)
+ return;
+
+ bool saveBack = _saveBack;
+ _saveBack = false;
+
+ if (rectListCount == -1) {
+ sDrawPic(srcPic, destPic, Common::Point());
+ } else {
+ for (int i = rectListCount - 1; i >= 0; --i) {
+ _clipPtr = &rectList[i];
+ sDrawPic(srcPic, destPic, Common::Point());
+ }
+ }
+
+ _saveBack = saveBack;
+}
+
+void GraphicsManager::clearPalette() {
+ byte palette[768];
+ Common::fill(&palette[0], &palette[768], 0);
+ g_system->getPaletteManager()->setPalette(&palette[0], 0, 256);
+}
+
+void GraphicsManager::setPalette(const byte *palette, int start, int count) {
+ g_system->getPaletteManager()->setPalette(palette, start, count);
+ _vm->_eventsManager._gameData._hasPalette = false;
+}
+
+void GraphicsManager::setPalette128(const byte *palette, int start, int count) {
+ byte rgb[3];
+ g_system->getPaletteManager()->grabPalette(&rgb[0], 128, 1);
+ g_system->getPaletteManager()->setPalette(palette, start, count);
+ g_system->getPaletteManager()->setPalette(&rgb[0], 128, 1);
+}
+
+
+void GraphicsManager::resetPalette() {
+ for (int i = 0; i < 256; ++i)
+ setColor(i, 0, 0, 0);
+
+ _vm->_eventsManager._intPtr._hasPalette = true;
+}
+
+void GraphicsManager::setColor(int idx, byte r, byte g, byte b) {
+ byte *vgaP = &_VGAColors[idx * 3];
+ vgaP[0] = r;
+ vgaP[1] = g;
+ vgaP[2] = b;
+
+ _vm->_eventsManager._intPtr._palStartIndex = MIN(_vm->_eventsManager._intPtr._palStartIndex, idx);
+ _vm->_eventsManager._intPtr._palEndIndex = MAX(_vm->_eventsManager._intPtr._palEndIndex, idx);
+}
+
+void GraphicsManager::setOneColor(int idx, byte r, byte g, byte b) {
+ byte palEntry[3];
+ palEntry[0] = r;
+ palEntry[1] = g;
+ palEntry[2] = b;
+ g_system->getPaletteManager()->setPalette(&palEntry[0], idx, 1);
+}
+
+void GraphicsManager::setColors(int start, int count, const byte *pal) {
+ for (int i = 0; i < count; ++i) {
+ if ((i + start) != 128) {
+ const byte *rgb = pal + i * 3;
+ setColor(i + start, rgb[0], rgb[1], rgb[2]);
+ }
+ }
+
+ _vm->_eventsManager._intPtr._hasPalette = true;
+}
+
+void GraphicsManager::screenReset() {
+ resetPalette();
+
+ _backgroundPage = NULL;
+ (*_vPort)->setupViewPort(NULL);
+ fillPic(*_vPort, 0);
+
+ _vm->flipPageAndWait();
+}
+
+void GraphicsManager::fadeDownICF1(int steps) {
+ if (steps > 0) {
+ int stepAmount = _vm->_voy._fadingAmount2 / steps;
+
+ for (int idx = 0; idx < steps; ++idx) {
+ _vm->_voy._fadingAmount2 -= stepAmount;
+ _vm->_eventsManager.delay(1);
+ }
+ }
+
+ _vm->_voy._fadingAmount2 = 0;
+}
+
+void GraphicsManager::fadeUpICF1(int steps) {
+ if (steps > 0) {
+ int stepAmount = (63 - _vm->_voy._fadingAmount2) / steps;
+
+ for (int idx = 0; idx < steps; ++idx) {
+ _vm->_voy._fadingAmount2 += stepAmount;
+ _vm->_eventsManager.delay(1);
+ }
+ }
+
+ _vm->_voy._fadingAmount2 = 63;
+}
+
+void GraphicsManager::fadeDownICF(int steps) {
+ if (steps > 0) {
+ _vm->_eventsManager.hideCursor();
+ int stepAmount1 = _vm->_voy._fadingAmount1 / steps;
+ int stepAmount2 = _vm->_voy._fadingAmount2 / steps;
+
+ for (int idx = 0; idx < steps; ++idx) {
+ _vm->_voy._fadingAmount1 -= stepAmount1;
+ _vm->_voy._fadingAmount2 -= stepAmount2;
+ _vm->_eventsManager.delay(1);
+ }
+ }
+
+ _vm->_voy._fadingAmount1 = 0;
+ _vm->_voy._fadingAmount2 = 0;
+}
+
+void GraphicsManager::drawDot() {
+ for (int y = 0; y < 9; ++y) {
+ byte *pDest = (byte *)_screenSurface.getPixels() + DOT_LINE_START[y] + DOT_LINE_OFFSET[y];
+ Common::fill(pDest, pDest + DOT_LINE_LENGTH[y], 0x80);
+ }
+}
+
+void GraphicsManager::synchronize(Common::Serializer &s) {
+ s.syncBytes(&_VGAColors[0], PALETTE_SIZE);
+}
+
+} // End of namespace Voyeur
diff --git a/engines/voyeur/graphics.h b/engines/voyeur/graphics.h
new file mode 100644
index 0000000000..322b90970c
--- /dev/null
+++ b/engines/voyeur/graphics.h
@@ -0,0 +1,134 @@
+/* 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 VOYEUR_GRAPHICS_H
+#define VOYEUR_GRAPHICS_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/serializer.h"
+#include "graphics/surface.h"
+
+namespace Voyeur {
+
+#define SCREEN_WIDTH 320
+#define SCREEN_HEIGHT 200
+#define PALETTE_COUNT 256
+#define PALETTE_SIZE (256 * 3)
+
+class VoyeurEngine;
+class GraphicsManager;
+class DisplayResource;
+class PictureResource;
+class ViewPortResource;
+class ViewPortListResource;
+class FontResource;
+class FontInfoResource;
+class CMapResource;
+
+class DrawInfo {
+public:
+ int _penColor;
+ Common::Point _pos;
+ int _flags;
+public:
+ DrawInfo(int penColor, const Common::Point &pos, int flags);
+};
+
+typedef void (GraphicsManager::*GraphicMethodPtr)();
+typedef void (GraphicsManager::*ViewPortSetupPtr)(ViewPortResource *);
+typedef void (GraphicsManager::*ViewPortAddPtr)(ViewPortResource *, int idx, const Common::Rect &bounds);
+typedef void (GraphicsManager::*ViewPortRestorePtr)(ViewPortResource *);
+
+class GraphicsManager {
+public:
+ VoyeurEngine *_vm;
+ bool _palFlag;
+ byte _VGAColors[PALETTE_SIZE];
+ Common::Array<byte *> _colorChain;
+ PictureResource *_backgroundPage;
+ int _SVGAPage;
+ int _SVGAMode;
+ int _SVGAReset;
+ ViewPortListResource *_viewPortListPtr;
+ ViewPortResource **_vPort;
+ bool _MCGAMode;
+ bool _saveBack;
+ Common::Rect *_clipPtr;
+ int _screenOffset;
+ uint _planeSelect;
+ int _sImageShift;
+ Graphics::Surface _screenSurface;
+ CMapResource *_backColors;
+ FontInfoResource *_fontPtr;
+ PictureResource *_fontChar;
+ DrawInfo *_drawPtr;
+ DrawInfo _defaultDrawInfo;
+ bool _drawTextPermFlag;
+private:
+ static void fadeIntFunc();
+ static void vDoCycleInt();
+
+ void restoreBack(Common::Array<Common::Rect> &rectList, int rectListCount,
+ PictureResource *srcPic, PictureResource *destPic);
+public:
+ GraphicsManager();
+ ~GraphicsManager();
+ void setVm(VoyeurEngine *vm) { _vm = vm; }
+ void sInitGraphics();
+
+ void setupMCGASaveRect(ViewPortResource *viewPort);
+ void addRectOptSaveRect(ViewPortResource *viewPort, int idx, const Common::Rect &bounds);
+ void restoreMCGASaveRect(ViewPortResource *viewPort);
+ void addRectNoSaveBack(ViewPortResource *viewPort, int idx, const Common::Rect &bounds);
+
+ void sDrawPic(DisplayResource *srcDisplay, DisplayResource *destDisplay, const Common::Point &initialOffset);
+ void fillPic(DisplayResource *display, byte onOff = 0);
+ void sDisplayPic(PictureResource *pic);
+ void drawANumber(DisplayResource *display, int num, const Common::Point &pt);
+ void flipPage();
+ void clearPalette();
+ void setPalette(const byte *palette, int start, int count);
+ void setPalette128(const byte *palette, int start, int count);
+ void resetPalette();
+ void setColor(int idx, byte r, byte g, byte b);
+ void setOneColor(int idx, byte r, byte g, byte b);
+ void setColors(int start, int count, const byte *pal);
+ void screenReset();
+ void fadeDownICF1(int steps);
+ void fadeUpICF1(int steps);
+ void fadeDownICF(int steps);
+ void drawDot();
+
+ /**
+ * Synchronizes the game data
+ */
+ void synchronize(Common::Serializer &s);
+
+ // Methods in the original that are stubbed in ScummVM
+ void EMSMapPageHandle(int v1, int v2, int v3) {}
+};
+
+} // End of namespace Voyeur
+
+#endif /* VOYEUR_GRAPHICS_H */
diff --git a/engines/voyeur/module.mk b/engines/voyeur/module.mk
new file mode 100644
index 0000000000..aab254cf36
--- /dev/null
+++ b/engines/voyeur/module.mk
@@ -0,0 +1,23 @@
+MODULE := engines/voyeur
+
+MODULE_OBJS := \
+ animation.o \
+ data.o \
+ debugger.o \
+ detection.o \
+ events.o \
+ files.o \
+ files_threads.o \
+ graphics.o \
+ sound.o \
+ staticres.o \
+ voyeur.o \
+ voyeur_game.o
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_VOYEUR), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
diff --git a/engines/voyeur/sound.cpp b/engines/voyeur/sound.cpp
new file mode 100644
index 0000000000..e81d2891fb
--- /dev/null
+++ b/engines/voyeur/sound.cpp
@@ -0,0 +1,85 @@
+/* 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 "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+#include "common/memstream.h"
+#include "voyeur/sound.h"
+#include "voyeur/staticres.h"
+
+namespace Voyeur {
+
+SoundManager::SoundManager(Audio::Mixer *mixer) {
+ _mixer = mixer;
+ _vocOffset = 0;
+}
+
+void SoundManager::playVOCMap(byte *voc, int vocSize) {
+ Common::MemoryReadStream *dataStream = new Common::MemoryReadStream(voc, vocSize, DisposeAfterUse::NO);
+ Audio::AudioStream *audioStream = Audio::makeVOCStream(dataStream, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
+
+ _mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, audioStream);
+}
+
+void SoundManager::abortVOCMap() {
+ _mixer->stopHandle(_soundHandle);
+}
+
+void SoundManager::stopVOCPlay() {
+ _mixer->stopHandle(_soundHandle);
+ _vocOffset = 0;
+}
+
+void SoundManager::setVOCOffset(int offset) {
+ _vocOffset = offset;
+}
+
+Common::String SoundManager::getVOCFileName(int idx) {
+ return Common::String::format("%s.voc", SZ_FILENAMES[idx]);
+}
+
+void SoundManager::startVOCPlay(const Common::String &filename) {
+ Common::File f;
+ if (!f.open(filename))
+ error("Could not find voc file - %s", filename.c_str());
+
+ Audio::SeekableAudioStream *audioStream = Audio::makeVOCStream(f.readStream(f.size()),
+ Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
+
+ _mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, audioStream);
+ audioStream->seek(Audio::Timestamp(_vocOffset * 1000, 11025));
+}
+
+void SoundManager::startVOCPlay(int soundId) {
+ startVOCPlay(getVOCFileName(soundId));
+}
+
+int SoundManager::getVOCStatus() {
+ return _mixer->isSoundHandleActive(_soundHandle);
+}
+
+uint32 SoundManager::getVOCFrame() {
+ Audio::Timestamp timestamp = _mixer->getElapsedTime(_soundHandle);
+ return timestamp.secs();
+}
+
+} // End of namespace Voyeur
diff --git a/engines/voyeur/sound.h b/engines/voyeur/sound.h
new file mode 100644
index 0000000000..ca05b0bfe0
--- /dev/null
+++ b/engines/voyeur/sound.h
@@ -0,0 +1,60 @@
+/* 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 VOYEUR_SOUND_H
+#define VOYEUR_SOUND_H
+
+#include "common/scummsys.h"
+#include "common/str.h"
+#include "audio/mixer.h"
+#include "audio/decoders/voc.h"
+#include "voyeur/files.h"
+
+namespace Voyeur {
+
+class SoundManager {
+private:
+ VoyeurEngine *_vm;
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _soundHandle;
+ int _vocOffset;
+public:
+ SoundManager(Audio::Mixer *mixer);
+ void setVm(VoyeurEngine *vm) { _vm = vm; }
+
+ void playVOCMap(byte *voc, int vocSize);
+ void stopVOCPlay();
+ void abortVOCMap();
+ void setVOCOffset(int offset);
+ Common::String getVOCFileName(int idx);
+ void startVOCPlay(const Common::String &filename);
+ void startVOCPlay(int soundId);
+ int getVOCStatus();
+ uint32 getVOCFrame();
+
+ // Methods in the original that are stubbed in ScummVM
+ void continueVocMap() {}
+};
+
+} // End of namespace Voyeur
+
+#endif /* VOYEUR_SOUND_H */
diff --git a/engines/voyeur/staticres.cpp b/engines/voyeur/staticres.cpp
new file mode 100644
index 0000000000..9025426c57
--- /dev/null
+++ b/engines/voyeur/staticres.cpp
@@ -0,0 +1,142 @@
+/* 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 "voyeur/staticres.h"
+
+namespace Voyeur {
+
+const int COMPUTER_DEFAULTS[] = {
+ 18, 1, 0, 1, 33, 0, 998, -1, 18, 2, 0, 1, 41, 0,
+ 998, -1, 18, 3, 0, 1, 47, 0, 998, -1, 18, 4, 0,
+ 1, 53, 0, 998, -1, 18, 5, 0, 1, 46, 0, 998, -1,
+ 18, 6, 0, 1, 50, 0, 998, -1, 18, 7, 0, 1, 40, 0,
+ 998, -1, 18, 8, 0, 1, 43, 0, 998, -1, 19, 1, 0,
+ 2, 28, 0, 998, -1
+};
+
+const int RESOLVE_TABLE[] = {
+ 0x2A00, 0x4A00, 0x1000, 0x4B00, 0x2C00, 0x4F00, 0x1400, 0x5000,
+ 0x1700, 0x5100, 0x1800, 0x5200, 0x3300, 0x5400, 0x3700, 0x5500,
+ 0x1A00, 0x1C00, 0x1E00, 0x1F00, 0x2100, 0x2200, 0x2400, 0x2700,
+ 0x2B00, 0x1100, 0x4C00, 0x1200, 0x4D00, 0x1300, 0x4E00, 0x2E00,
+ 0x1900, 0x3200, 0x3400, 0x3800, 0x2800, 0x3E00, 0x4100, 0x2900,
+ 0x4400, 0x4600, 0x5300, 0x3900, 0x7600, 0x7200, 0x7300, 0x7400,
+ 0x7500
+};
+
+const int LEVEL_H[] = {
+ 4, 7, 7, 8, 9, 10, 2, 2, 4, 8, 8, 9, 9, 10, 10, 11, 11
+};
+
+const int LEVEL_M[] = {
+ 0, 0, 30, 0, 30, 0, 0, 0, 30, 0, 30, 0, 45, 0, 30, 0, 30
+};
+
+const int BLIND_TABLE[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 9, 10, 11, 1, 11, 5, 12,
+ 13, 16, 15, 16, 17, 18, 5, 6, 18, 17, 13, 13, 14, 14,
+ 5, 12, 6, 6, 13, 14, 13
+};
+
+const int COMP_BUT_TABLE[] = {
+ 269, 128, 307, 163,
+ 269, 128, 307, 163,
+ 68, 79, 98, 102,
+ 68, 79, 98, 102,
+ 68, 79, 98, 102,
+ 68, 79, 98, 102,
+ 248, 138, 291, 163,
+ 83, 132, 143, 156,
+ 248, 138, 291, 163,
+ 83, 132, 143, 156,
+ 83, 132, 143, 156,
+ 248, 138, 291, 163,
+ 68, 79, 98, 102,
+ 68, 79, 98, 102
+};
+
+const char *const SZ_FILENAMES[] = {
+ "A2110100", nullptr, "A2300100", nullptr, "B1220100", nullptr, "C1220100", nullptr,
+ "C1290100", nullptr, "D1220100", nullptr, "D1270100", nullptr, "E1210100", nullptr,
+ "E1260100", nullptr, "E1280100", nullptr, "E1325100", nullptr, "F1200100", nullptr,
+ "G1250100", nullptr, "G1260100", nullptr, "H1200100", nullptr, "H1230100", nullptr,
+ "H1310100", nullptr, "I1300100", nullptr, "J1220100", nullptr, "J1230100", nullptr,
+ "J1320100", nullptr, "K1260100", nullptr, "K1280100", nullptr, "K1325100", nullptr,
+ "L1210100", nullptr, "L1280100", nullptr, "L1290100", nullptr, "L1300100", nullptr,
+ "L1310100", nullptr, "M1260100", nullptr, "M1310100", nullptr, "N1210100", nullptr,
+ "N1225100", nullptr, "N1275510", nullptr, "N1280510", nullptr, "N1325100", nullptr,
+ "O1230100", nullptr, "O1260100", nullptr, "O1260520", nullptr, "O1280100", nullptr,
+ "O1325540", nullptr, "P1276710", nullptr, "P1280540", nullptr, "P1280740", nullptr,
+ "P1290510", nullptr, "P1325100", nullptr, "P1325300", nullptr, "P1325520", nullptr,
+ "Q1230100", nullptr, "Q1240530", nullptr, "Q1240730", nullptr, "Q1260100", nullptr,
+ "Q1260520", nullptr, "Q1260720", nullptr, "Q1325100", nullptr, "R1280540", nullptr,
+ "Z1110510", nullptr, "Z1110520", nullptr, "Z1110530", nullptr, "Z1110540", nullptr,
+ "Z1110545", nullptr, "Z2320100", nullptr, "Z2905300", nullptr, "Z3110100", nullptr,
+ "Z3115510", nullptr, "Z3115520", nullptr, "Z3115530", nullptr, "Z3115540", nullptr,
+ "Z4915100", nullptr, "Z4915200", nullptr, "Z4915300",
+ nullptr, nullptr, nullptr, nullptr, nullptr,
+ "MMARG", "MZACK", "MREED", "MJESSI", "MCHLOE", "MCAMERA", "MENDCRED",
+ "NEWCALL2", "PHONE1", "PHONE2", "PHONE3", "PHONE6", "PHONE8",
+ "B1300100", "C1250100", "C1320100", "D1320100", "E1210200", "E1260200",
+ "E1280200", "E1310100", "G1230100", "G1300100", "I1210100", "I1270100",
+ "I1280100", "J1250100", "J1280100", "K1260200", "K1270100", "K1325200",
+ "L1240100", "M1200100", "M1230100", "M1290100", "N1250100", "N1260100",
+ "N1280100", "O1250510", "O1290510", "O1320510", "O1320710", "P1240100",
+ "P1240530", "P1260100", "P1270100", "P1280100", "P1280530", "P1320530",
+ "Q1240100", "E1325100"
+};
+
+const char *const SATURDAY = "Saturday";
+const char *const SUNDAY = "Sunday";
+const char *const MONDAY = "Monday Morning";
+const char *const AM = "am";
+const char *const PM = "pm";
+
+const char *const START_OF_MESSAGE = "*** Start of Message ***";
+const char *const END_OF_MESSAGE = "*** End of Message ***";
+
+const char *const EVENT_TYPE_STRINGS[4] = { "Video", "Audio", "Evidence", "Computer" };
+
+int DOT_LINE_START[9] = {
+ 0xE880, 0xE9C0, 0xEB00, 0xEC40, 0xED80, 0xEEC0, 0xF000, 0xF140, 0xF280
+};
+int DOT_LINE_OFFSET[9] = {
+ 144, 143, 142, 141, 141, 141, 142, 143, 144
+};
+int DOT_LINE_LENGTH[9] = {
+ 5, 7, 9, 11, 11, 11, 9, 7, 5
+};
+
+const char *const PIRACY_MESSAGE[] = {
+ "It is illegal to make",
+ "unauthorized copies of",
+ "this software. Duplication",
+ "of this software for any",
+ "reason including sale,",
+ "loan, rental, or gift is a",
+ "crime. Penalties include",
+ "fines of up to $50,000",
+ "and jail terms up to",
+ "5 years."
+};
+
+} // End of namespace Voyeur
diff --git a/engines/voyeur/staticres.h b/engines/voyeur/staticres.h
new file mode 100644
index 0000000000..20bffe01e3
--- /dev/null
+++ b/engines/voyeur/staticres.h
@@ -0,0 +1,63 @@
+/* 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 VOYEUR_STATICRES_H
+#define VOYEUR_STATICRES_H
+
+#include "common/scummsys.h"
+
+namespace Voyeur {
+
+extern const int COMPUTER_DEFAULTS[];
+
+extern const int RESOLVE_TABLE[];
+
+extern const int LEVEL_H[];
+
+extern const int LEVEL_M[];
+
+extern const int BLIND_TABLE[];
+
+extern const int COMP_BUT_TABLE[];
+
+extern const char *const SZ_FILENAMES[];
+
+extern const char *const SATURDAY;
+extern const char *const SUNDAY;
+extern const char *const MONDAY;
+extern const char *const AM;
+extern const char *const PM;
+
+extern const char *const START_OF_MESSAGE;
+extern const char *const END_OF_MESSAGE;
+
+extern const char *const EVENT_TYPE_STRINGS[4];
+
+extern int DOT_LINE_START[9];
+extern int DOT_LINE_OFFSET[9];
+extern int DOT_LINE_LENGTH[9];
+
+extern const char *const PIRACY_MESSAGE[];
+
+} // End of namespace Voyeur
+
+#endif
diff --git a/engines/voyeur/voyeur.cpp b/engines/voyeur/voyeur.cpp
new file mode 100644
index 0000000000..d7fa92f1cb
--- /dev/null
+++ b/engines/voyeur/voyeur.cpp
@@ -0,0 +1,919 @@
+/* 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 "voyeur/voyeur.h"
+#include "voyeur/animation.h"
+#include "voyeur/graphics.h"
+#include "voyeur/staticres.h"
+#include "common/scummsys.h"
+#include "common/config-manager.h"
+#include "common/debug-channels.h"
+#include "graphics/palette.h"
+#include "graphics/scaler.h"
+#include "graphics/thumbnail.h"
+
+namespace Voyeur {
+
+VoyeurEngine *g_vm;
+
+VoyeurEngine::VoyeurEngine(OSystem *syst, const VoyeurGameDescription *gameDesc) : Engine(syst),
+ _gameDescription(gameDesc), _randomSource("Voyeur"), _soundManager(_mixer),
+ _defaultFontInfo(3, 0xff, 0xff, 0, 0, ALIGN_LEFT, 0, Common::Point(), 1, 1,
+ Common::Point(1, 1), 1, 0, 0) {
+ _bVoy = NULL;
+ _iForceDeath = -1;
+ _controlPtr = NULL;
+ _stampFlags = 0;
+ _playStampGroupId = _currentVocId = 0;
+ _audioVideoId = -1;
+ _checkTransitionId = -1;
+ _gameHour = 0;
+ _gameMinute = 0;
+ _flashTimeVal = 0;
+ _flashTimeFlag = false;
+ _timeBarVal = -1;
+ _checkPhoneVal = 0;
+ _voyeurArea = AREA_NONE;
+ _loadGameSlot = -1;
+
+ DebugMan.addDebugChannel(kDebugScripts, "scripts", "Game scripts");
+
+ initializeManagers();
+}
+
+VoyeurEngine::~VoyeurEngine() {
+ delete _bVoy;
+}
+
+Common::Error VoyeurEngine::run() {
+ ESP_Init();
+ globalInitBolt();
+
+ // The original allows the victim to be explicitly specified via the command line.
+ // We don't currently support this in ScummVM, but I'm leaving the code below
+ // in case we ever want to make use of it.
+ if (_iForceDeath >= 1 && _iForceDeath <= 4)
+ _voy._eventFlags |= EVTFLAG_VICTIM_PRESET;
+
+ _eventsManager.resetMouse();
+ if (doHeadTitle()) {
+ playStamp();
+ if (!shouldQuit())
+ doTailTitle();
+ }
+
+ //doHeadTitle();
+
+ return Common::kNoError;
+}
+
+
+int VoyeurEngine::getRandomNumber(int maxNumber) {
+ return _randomSource.getRandomNumber(maxNumber);
+}
+
+void VoyeurEngine::initializeManagers() {
+ _debugger.setVm(this);
+ _eventsManager.setVm(this);
+ _filesManager.setVm(this);
+ _graphicsManager.setVm(this);
+ _soundManager.setVm(this);
+ _voy.setVm(this);
+}
+
+void VoyeurEngine::ESP_Init() {
+ ThreadResource::init();
+
+ if (ConfMan.hasKey("save_slot"))
+ _loadGameSlot = ConfMan.getInt("save_slot");
+}
+
+void VoyeurEngine::globalInitBolt() {
+ initBolt();
+
+ _filesManager.openBoltLib("bvoy.blt", _bVoy);
+ _bVoy->getBoltGroup(0x000);
+ _bVoy->getBoltGroup(0x100);
+
+ _graphicsManager._fontPtr = &_defaultFontInfo;
+ _graphicsManager._fontPtr->_curFont = _bVoy->boltEntry(0x101)._fontResource;
+ assert(_graphicsManager._fontPtr->_curFont);
+
+ // Setup default flags
+ _voy._fadeICF0 = false;
+ _voy._viewBounds = nullptr;
+ _voy._curICF0 = _graphicsManager._palFlag ? 0xFFFFA5E0 : 0x5F90;
+
+ _eventsManager.addFadeInt();
+}
+
+void VoyeurEngine::initBolt() {
+ vInitInterrupts();
+ _graphicsManager.sInitGraphics();
+ _eventsManager.vInitColor();
+ initInput();
+}
+
+void VoyeurEngine::vInitInterrupts() {
+ _eventsManager._intPtr._palette = &_graphicsManager._VGAColors[0];
+}
+
+void VoyeurEngine::initInput() {
+}
+
+bool VoyeurEngine::doHeadTitle() {
+// char dest[144];
+
+ _eventsManager.startMainClockInt();
+
+ if (_loadGameSlot == -1) {
+ // Show starting screen
+ if (_bVoy->getBoltGroup(0x500)) {
+ showConversionScreen();
+ _bVoy->freeBoltGroup(0x500);
+
+ if (shouldQuit())
+ return false;
+ }
+
+ if (ConfMan.getBool("copy_protection")) {
+ // Display lock screen
+ bool result = doLock();
+ if (!result || shouldQuit())
+ return false;
+ }
+
+ // Show the title screen
+ _eventsManager.getMouseInfo();
+ showTitleScreen();
+ if (shouldQuit())
+ return false;
+
+ // Opening
+ _eventsManager.getMouseInfo();
+ doOpening();
+ if (shouldQuit())
+ return false;
+
+ _eventsManager.getMouseInfo();
+ doTransitionCard("Saturday Afternoon", "Player's Apartment");
+ _eventsManager.delayClick(90);
+
+ if (_voy._eventFlags & EVTFLAG_VICTIM_PRESET) {
+ // Preset victim turned on, so add a default set of incriminating videos
+ _voy.addEvent(18, 1, EVTYPE_VIDEO, 33, 0, 998, -1);
+ _voy.addEvent(18, 2, EVTYPE_VIDEO, 41, 0, 998, -1);
+ _voy.addEvent(18, 3, EVTYPE_VIDEO, 47, 0, 998, -1);
+ _voy.addEvent(18, 4, EVTYPE_VIDEO, 53, 0, 998, -1);
+ _voy.addEvent(18, 5, EVTYPE_VIDEO, 46, 0, 998, -1);
+ _voy.addEvent(18, 6, EVTYPE_VIDEO, 50, 0, 998, -1);
+ _voy.addEvent(18, 7, EVTYPE_VIDEO, 40, 0, 998, -1);
+ _voy.addEvent(18, 8, EVTYPE_VIDEO, 43, 0, 998, -1);
+ _voy.addEvent(19, 1, EVTYPE_AUDIO, 20, 0, 998, -1);
+ }
+ }
+
+ _voy._aptLoadMode = 140;
+ return true;
+}
+
+void VoyeurEngine::showConversionScreen() {
+ _graphicsManager._backgroundPage = _bVoy->boltEntry(0x502)._picResource;
+ (*_graphicsManager._vPort)->setupViewPort();
+ flipPageAndWait();
+
+ // Immediate palette load to show the initial screen
+ CMapResource *cMap = _bVoy->getCMapResource(0x503);
+ assert(cMap);
+ cMap->_steps = 0;
+ cMap->startFade();
+
+ // Wait briefly
+ _eventsManager.delayClick(150);
+ if (shouldQuit())
+ return;
+
+ // Fade out the screen
+ cMap = _bVoy->getCMapResource(0x5040000);
+ cMap->_steps = 30;
+ cMap->startFade();
+ if (shouldQuit())
+ return;
+
+ flipPageAndWaitForFade();
+
+ _graphicsManager.screenReset();
+}
+
+bool VoyeurEngine::doLock() {
+ bool result = true;
+ int buttonVocSize, wrongVocSize;
+ byte *buttonVoc = _filesManager.fload("button.voc", &buttonVocSize);
+ byte *wrongVoc = _filesManager.fload("wrong.voc", &wrongVocSize);
+
+ if (_bVoy->getBoltGroup(0x700)) {
+ _voy._viewBounds = _bVoy->boltEntry(0x704)._rectResource;
+
+ Common::String password = "3333";
+ PictureResource *cursorPic = _bVoy->getPictureResource(0x702);
+ assert(cursorPic);
+
+ // Get the mappings of keys on the keypad
+ byte *keyData = _bVoy->memberAddr(0x705);
+ int keyCount = READ_LE_UINT16(keyData);
+
+ _graphicsManager._backColors = _bVoy->getCMapResource(0x7010000);
+ _graphicsManager._backgroundPage = _bVoy->getPictureResource(0x700);
+ (*_graphicsManager._vPort)->setupViewPort();
+
+ _graphicsManager._backColors->startFade();
+ (*_graphicsManager._vPort)->_parent->_flags |= DISPFLAG_8;
+ _graphicsManager.flipPage();
+ _eventsManager.sWaitFlip();
+
+ while (!shouldQuit() && (_eventsManager._fadeStatus & 1))
+ _eventsManager.delay(1);
+
+ _eventsManager.setCursorColor(127, 0);
+ _graphicsManager.setColor(1, 64, 64, 64);
+ _graphicsManager.setColor(2, 96, 96, 96);
+ _graphicsManager.setColor(3, 160, 160, 160);
+ _graphicsManager.setColor(4, 224, 224, 224);
+
+ // Set up the cursor
+ _eventsManager.setCursor(cursorPic);
+ _eventsManager.showCursor();
+
+ _eventsManager._intPtr._hasPalette = true;
+
+ _graphicsManager._fontPtr->_curFont = _bVoy->boltEntry(0x708)._fontResource;
+ _graphicsManager._fontPtr->_fontSaveBack = 0;
+ _graphicsManager._fontPtr->_fontFlags = 0;
+
+ Common::String dateString = "ScummVM";
+ Common::String displayString = Common::String::format("Last Play %s", dateString.c_str());
+
+ bool firstLoop = true;
+ bool breakFlag = false;
+ bool flag = false;
+ while (!breakFlag && !shouldQuit()) {
+ (*_graphicsManager._vPort)->setupViewPort();
+ flipPageAndWait();
+
+ // Display the last play time
+ _graphicsManager._fontPtr->_pos = Common::Point(0, 97);
+ _graphicsManager._fontPtr->_justify = ALIGN_CENTRE;
+ _graphicsManager._fontPtr->_justifyWidth = 384;
+ _graphicsManager._fontPtr->_justifyHeight = 97;
+
+ (*_graphicsManager._vPort)->drawText(displayString);
+ flipPageAndWait();
+
+ if (firstLoop) {
+ firstLoop = false;
+ displayString = "";
+ }
+
+ // Loop for getting key presses
+ int key;
+ do {
+ do {
+ // Scan through the list of key rects to check if a keypad key is highlighted
+ key = -1;
+ Common::Point mousePos = _eventsManager.getMousePos() +
+ Common::Point(30, 20);
+
+ for (int keyIndex = 0; keyIndex < keyCount; ++keyIndex) {
+ int x1 = READ_LE_UINT16(keyData + (((keyIndex << 2) + 1) << 1));
+ int x2 = READ_LE_UINT16(keyData + (((keyIndex << 2) + 3) << 1));
+ int y1 = READ_LE_UINT16(keyData + (((keyIndex << 2) + 2) << 1));
+ int y2 = READ_LE_UINT16(keyData + (((keyIndex << 2) + 4) << 1));
+
+ if (mousePos.x >= x1 && mousePos.x <= x2 && mousePos.y >= y1 && mousePos.y <= y2) {
+ key = keyIndex;
+ }
+ }
+
+ _eventsManager.setCursorColor(127, (key == -1) ? 0 : 1);
+ _eventsManager._intPtr._hasPalette = true;
+
+ _eventsManager.delay(1);
+ } while (!shouldQuit() && !_eventsManager._mouseClicked);
+ _eventsManager._mouseClicked = false;
+ } while (!shouldQuit() && key == -1);
+
+ _soundManager.abortVOCMap();
+ _soundManager.playVOCMap(buttonVoc, buttonVocSize);
+
+ while (_soundManager.getVOCStatus()) {
+ if (shouldQuit())
+ break;
+
+ _soundManager.continueVocMap();
+ _eventsManager.delay(1);
+ }
+
+ // Process the key
+ if (key < 10) {
+ // Numeric key
+ if (displayString.size() < 10) {
+ displayString += '0' + key;
+ continue;
+ }
+ } else if (key == 10) {
+ // Accept key
+ if (!flag) {
+ if ((password.empty() && displayString.empty()) || (password == displayString)) {
+ breakFlag = true;
+ result = true;
+ break;
+ }
+ } else {
+ if (displayString.size() > 0) {
+ result = true;
+ breakFlag = true;
+ break;
+ }
+ }
+ } else if (key == 11) {
+ // New code
+ if ((password.empty() && displayString.empty()) || (password != displayString)) {
+ (*_graphicsManager._vPort)->setupViewPort();
+ flag = true;
+ displayString = "";
+ continue;
+ }
+ } else if (key == 12) {
+ // Exit keyword
+ breakFlag = true;
+ result = false;
+ break;
+ } else {
+ continue;
+ }
+
+ _soundManager.playVOCMap(wrongVoc, wrongVocSize);
+ }
+
+ _graphicsManager.fillPic(*_graphicsManager._vPort);
+ flipPageAndWait();
+ _graphicsManager.resetPalette();
+
+ _voy._viewBounds = nullptr;
+ _bVoy->freeBoltGroup(0x700);
+ }
+
+ _eventsManager.hideCursor();
+
+ delete[] buttonVoc;
+ delete[] wrongVoc;
+
+ return result;
+}
+
+void VoyeurEngine::showTitleScreen() {
+ if (_bVoy->getBoltGroup(0x500)) {
+ _graphicsManager._backgroundPage = _bVoy->getPictureResource(0x500);
+
+ (*_graphicsManager._vPort)->setupViewPort();
+ flipPageAndWait();
+
+ // Immediate palette load to show the initial screen
+ CMapResource *cMap = _bVoy->getCMapResource(0x501);
+ assert(cMap);
+ cMap->_steps = 60;
+ cMap->startFade();
+
+ // Wait briefly
+ _eventsManager.delayClick(200);
+ if (shouldQuit())
+ return;
+
+ // Fade out the screen
+ cMap = _bVoy->getCMapResource(0x504);
+ cMap->_steps = 30;
+ cMap->startFade();
+
+ flipPageAndWaitForFade();
+ if (shouldQuit())
+ return;
+
+ _graphicsManager.screenReset();
+ _eventsManager.delayClick(200);
+
+ // Voyeur title
+ playRL2Video("a1100100.rl2");
+ _graphicsManager.screenReset();
+
+ _bVoy->freeBoltGroup(0x500);
+ }
+}
+
+void VoyeurEngine::doOpening() {
+ _graphicsManager.screenReset();
+
+ if (!_bVoy->getBoltGroup(0x200, true))
+ return;
+
+ byte *frameTable = _bVoy->memberAddr(0x215);
+ byte *xyTable = _bVoy->memberAddr(0x216);
+// byte *whTable = _bVoy->memberAddr(0x217);
+ int frameIndex = 0;
+ bool creditShow = true;
+ PictureResource *textPic = nullptr;
+ Common::Point textPos;
+
+ _voy._vocSecondsOffset = 0;
+ _voy._RTVNum = 0;
+ _voy._audioVisualStartTime = _voy._RTVNum;
+ _voy._eventFlags |= EVTFLAG_RECORDING;
+ _gameHour = 4;
+ _gameMinute = 0;
+ _audioVideoId = 1;
+ _eventsManager._videoDead = -1;
+ _voy.addVideoEventStart();
+
+ _voy._eventFlags &= ~EVTFLAG_TIME_DISABLED;
+
+ for (int i = 0; i < 256; ++i)
+ _graphicsManager.setColor(i, 8, 8, 8);
+
+ _eventsManager._intPtr._hasPalette = true;
+ (*_graphicsManager._vPort)->setupViewPort();
+ flipPageAndWait();
+
+ RL2Decoder decoder;
+ decoder.loadFile("a2300100.rl2");
+ decoder.start();
+
+ while (!shouldQuit() && !decoder.endOfVideo() && !_eventsManager._mouseClicked) {
+ if (decoder.hasDirtyPalette()) {
+ const byte *palette = decoder.getPalette();
+ _graphicsManager.setPalette(palette, 0, 256);
+ }
+
+ if (decoder.needsUpdate()) {
+ const Graphics::Surface *frame = decoder.decodeNextFrame();
+
+ Common::copy((const byte *)frame->getPixels(), (const byte *)frame->getPixels() + 320 * 200,
+ (byte *)_graphicsManager._screenSurface.getPixels());
+
+ if (decoder.getCurFrame() >= (int32)READ_LE_UINT32(frameTable + frameIndex * 4)) {
+ if (creditShow) {
+ // Show a credit
+ textPic = _bVoy->boltEntry(frameIndex / 2 + 0x202)._picResource;
+ textPos = Common::Point(READ_LE_UINT16(xyTable + frameIndex * 2),
+ READ_LE_UINT16(xyTable + (frameIndex + 1) * 2));
+
+ creditShow = false;
+ } else {
+ textPic = nullptr;
+
+ creditShow = true;
+ }
+
+ ++frameIndex;
+ }
+
+ if (textPic) {
+ _graphicsManager.sDrawPic(textPic, *_graphicsManager._vPort, textPos);
+ flipPageAndWait();
+ }
+ }
+
+ _eventsManager.getMouseInfo();
+ g_system->delayMillis(10);
+ }
+
+ if ((_voy._RTVNum - _voy._audioVisualStartTime) < 2)
+ _eventsManager.delay(60);
+
+ _voy._eventFlags |= EVTFLAG_TIME_DISABLED;
+ _voy.addVideoEventEnd();
+ _voy._eventFlags &= ~EVTFLAG_RECORDING;
+
+ _bVoy->freeBoltGroup(0x200);
+}
+
+void VoyeurEngine::playRL2Video(const Common::String &filename) {
+ RL2Decoder decoder;
+ decoder.loadFile(filename);
+ decoder.start();
+
+ while (!shouldQuit() && !decoder.endOfVideo() && !_eventsManager._mouseClicked) {
+ if (decoder.hasDirtyPalette()) {
+ const byte *palette = decoder.getPalette();
+ _graphicsManager.setPalette(palette, 0, 256);
+ }
+
+ if (decoder.needsUpdate()) {
+ const Graphics::Surface *frame = decoder.decodeNextFrame();
+
+ Common::copy((const byte *)frame->getPixels(), (const byte *)frame->getPixels() + 320 * 200,
+ (byte *)_graphicsManager._screenSurface.getPixels());
+ }
+
+ _eventsManager.getMouseInfo();
+ g_system->delayMillis(10);
+ }
+}
+
+void VoyeurEngine::playAVideo(int videoId) {
+ playAVideoDuration(videoId, 9999);
+}
+
+void VoyeurEngine::playAVideoDuration(int videoId, int duration) {
+ int totalFrames = duration * 10;
+
+ if (videoId == -1)
+ return;
+
+ PictureResource *pic = NULL;
+ if (videoId == 42) {
+ _eventsManager._videoDead = 0;
+ pic = _bVoy->boltEntry(0xE00 + _eventsManager._videoDead)._picResource;
+ }
+
+ RL2Decoder decoder;
+ decoder.loadVideo(videoId);
+
+ decoder.seek(Audio::Timestamp(_voy._vocSecondsOffset * 1000));
+ decoder.start();
+ int endFrame = decoder.getCurFrame() + totalFrames;
+
+ _eventsManager.getMouseInfo();
+ if (!_voy._fadeICF0)
+ _eventsManager.startCursorBlink();
+
+ while (!shouldQuit() && !decoder.endOfVideo() && !_eventsManager._mouseClicked &&
+ (decoder.getCurFrame() < endFrame)) {
+ if (decoder.needsUpdate()) {
+ const Graphics::Surface *frame = decoder.decodeNextFrame();
+
+ Common::copy((const byte *)frame->getPixels(), (const byte *)frame->getPixels() + 320 * 200,
+ (byte *)_graphicsManager._screenSurface.getPixels());
+ if (_voy._eventFlags & EVTFLAG_RECORDING)
+ _graphicsManager.drawDot();
+ }
+
+ if (decoder.hasDirtyPalette()) {
+ const byte *palette = decoder.getPalette();
+ _graphicsManager.setPalette(palette, 0, decoder.getPaletteCount());
+ _graphicsManager.setOneColor(128, 220, 20, 20);
+ }
+
+ _eventsManager.getMouseInfo();
+ g_system->delayMillis(10);
+ }
+
+ // RL2 finished
+ _graphicsManager.screenReset();
+ _voy._eventFlags &= ~EVTFLAG_RECORDING;
+
+ if (_voy._eventFlags & EVTFLAG_8) {
+ assert(pic);
+ byte *imgData = (*_graphicsManager._vPort)->_currentPic->_imgData;
+ (*_graphicsManager._vPort)->_currentPic->_imgData = pic->_imgData;
+ pic->_imgData = imgData;
+ _voy._eventFlags &= ~EVTFLAG_8;
+ }
+}
+
+void VoyeurEngine::playAudio(int audioId) {
+ _bVoy->getBoltGroup(0x7F00);
+ _graphicsManager._backgroundPage = _bVoy->boltEntry(0x7F00 +
+ BLIND_TABLE[audioId] * 2)._picResource;
+ _graphicsManager._backColors = _bVoy->boltEntry(0x7F01 +
+ BLIND_TABLE[audioId] * 2)._cMapResource;
+
+ (*_graphicsManager._vPort)->setupViewPort();
+ _graphicsManager._backColors->startFade();
+ flipPageAndWaitForFade();
+
+ _voy._eventFlags &= ~EVTFLAG_TIME_DISABLED;
+ _soundManager.setVOCOffset(_voy._vocSecondsOffset);
+ Common::String filename = _soundManager.getVOCFileName(
+ audioId + 159);
+ _soundManager.startVOCPlay(filename);
+ _voy._eventFlags |= EVTFLAG_RECORDING;
+ _eventsManager.startCursorBlink();
+
+ while (!shouldQuit() && !_eventsManager._mouseClicked &&
+ _soundManager.getVOCStatus())
+ _eventsManager.delayClick(1);
+
+ _voy._eventFlags |= EVTFLAG_TIME_DISABLED;
+ _soundManager.stopVOCPlay();
+
+ _bVoy->freeBoltGroup(0x7F00);
+ (*_graphicsManager._vPort)->setupViewPort(NULL);
+
+ _voy._eventFlags &= ~EVTFLAG_RECORDING;
+ _voy._playStampMode = 129;
+}
+
+void VoyeurEngine::doTransitionCard(const Common::String &time, const Common::String &location) {
+ _graphicsManager.setColor(128, 16, 16, 16);
+ _graphicsManager.setColor(224, 220, 220, 220);
+ _eventsManager._intPtr._hasPalette = true;
+
+ (*_graphicsManager._vPort)->setupViewPort(NULL);
+ (*_graphicsManager._vPort)->fillPic(128);
+ _graphicsManager.flipPage();
+ _eventsManager.sWaitFlip();
+
+ flipPageAndWait();
+ (*_graphicsManager._vPort)->fillPic(128);
+
+ FontInfoResource &fi = *_graphicsManager._fontPtr;
+ fi._curFont = _bVoy->boltEntry(257)._fontResource;
+ fi._foreColor = 224;
+ fi._fontSaveBack = 0;
+ fi._pos = Common::Point(0, 116);
+ fi._justify = ALIGN_CENTRE;
+ fi._justifyWidth = 384;
+ fi._justifyHeight = 120;
+
+ (*_graphicsManager._vPort)->drawText(time);
+
+ if (!location.empty()) {
+ fi._pos = Common::Point(0, 138);
+ fi._justify = ALIGN_CENTRE;
+ fi._justifyWidth = 384;
+ fi._justifyHeight = 140;
+
+ (*_graphicsManager._vPort)->drawText(location);
+ }
+
+ flipPageAndWait();
+}
+
+void VoyeurEngine::saveLastInplay() {
+ // No implementation in ScummVM version
+}
+
+void VoyeurEngine::flipPageAndWait() {
+ (*_graphicsManager._vPort)->_flags |= DISPFLAG_8;
+ _graphicsManager.flipPage();
+ _eventsManager.sWaitFlip();
+}
+
+void VoyeurEngine::flipPageAndWaitForFade() {
+ flipPageAndWait();
+
+ while (!shouldQuit() && (_eventsManager._fadeStatus & 1))
+ _eventsManager.delay(1);
+}
+
+void VoyeurEngine::showEndingNews() {
+ _playStampGroupId = (_voy._incriminatedVictimNumber - 1) * 256 + 0x7700;
+ _voy._boltGroupId2 = (_controlPtr->_state->_victimIndex - 1) * 256 + 0x7B00;
+
+ _bVoy->getBoltGroup(_playStampGroupId);
+ _bVoy->getBoltGroup(_voy._boltGroupId2);
+
+ PictureResource *pic = _bVoy->boltEntry(_playStampGroupId)._picResource;
+ CMapResource *pal = _bVoy->boltEntry(_playStampGroupId + 1)._cMapResource;
+
+ (*_graphicsManager._vPort)->setupViewPort(pic);
+ pal->startFade();
+ flipPageAndWaitForFade();
+
+ _eventsManager.getMouseInfo();
+
+ for (int idx = 1; idx < 4; ++idx) {
+ if (idx == 3) {
+ pic = _bVoy->boltEntry(_voy._boltGroupId2)._picResource;
+ pal = _bVoy->boltEntry(_voy._boltGroupId2 + 1)._cMapResource;
+ } else {
+ pic = _bVoy->boltEntry(_playStampGroupId + idx * 2)._picResource;
+ pal = _bVoy->boltEntry(_playStampGroupId + idx * 2 + 1)._cMapResource;
+ }
+
+ (*_graphicsManager._vPort)->setupViewPort(pic);
+ pal->startFade();
+ flipPageAndWaitForFade();
+
+ _bVoy->freeBoltMember(_playStampGroupId + (idx - 1) * 2);
+ _bVoy->freeBoltMember(_playStampGroupId + (idx - 1) * 2 + 1);
+
+ Common::String fname = Common::String::format("news%d.voc", idx);
+ _soundManager.startVOCPlay(fname);
+
+ _eventsManager.getMouseInfo();
+ while (!shouldQuit() && !_eventsManager._mouseClicked &&
+ _soundManager.getVOCStatus()) {
+ _eventsManager.delay(1);
+ _eventsManager.getMouseInfo();
+ }
+
+ _soundManager.stopVOCPlay();
+ if (idx == 3)
+ _eventsManager.delay(3);
+
+ if (shouldQuit() || _eventsManager._mouseClicked)
+ break;
+ }
+
+ _bVoy->freeBoltGroup(_playStampGroupId);
+ _bVoy->freeBoltGroup(_voy._boltGroupId2);
+ _playStampGroupId = -1;
+ _voy._boltGroupId2 = -1;
+}
+
+/*------------------------------------------------------------------------*/
+
+Common::String VoyeurEngine::generateSaveName(int slot) {
+ return Common::String::format("%s.%03d", _targetName.c_str(), slot);
+}
+
+/**
+ * Returns true if it is currently okay to restore a game
+ */
+bool VoyeurEngine::canLoadGameStateCurrently() {
+ return _voyeurArea == AREA_APARTMENT;
+}
+
+/**
+ * Returns true if it is currently okay to save the game
+ */
+bool VoyeurEngine::canSaveGameStateCurrently() {
+ return _voyeurArea == AREA_APARTMENT;
+}
+
+/**
+ * Load the savegame at the specified slot index
+ */
+Common::Error VoyeurEngine::loadGameState(int slot) {
+ _loadGameSlot = slot;
+ return Common::kNoError;
+}
+
+void VoyeurEngine::loadGame(int slot) {
+ // Open up the save file
+ Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(generateSaveName(slot));
+ if (!saveFile)
+ return;
+
+ Common::Serializer serializer(saveFile, NULL);
+
+ // Store the current time index before the game is loaded
+ _checkTransitionId = _voy._transitionId;
+
+ // Stop any playing sound
+ _soundManager.stopVOCPlay();
+
+ // Read in the savegame header
+ VoyeurSavegameHeader header;
+ if (!header.read(saveFile))
+ return;
+ if (header._thumbnail)
+ header._thumbnail->free();
+ delete header._thumbnail;
+
+ synchronize(serializer);
+
+ delete saveFile;
+
+ // Show a transition card if the time index has changed
+ checkTransition();
+
+ // Load the apartment
+ _mainThread->loadTheApt();
+}
+
+/**
+ * Save the game to the given slot index, and with the given name
+ */
+Common::Error VoyeurEngine::saveGameState(int slot, const Common::String &desc) {
+ // Open the save file for writing
+ Common::OutSaveFile *saveFile = g_system->getSavefileManager()->openForSaving(generateSaveName(slot));
+ if (!saveFile)
+ return Common::kCreatingFileFailed;
+
+ // Write out the header
+ VoyeurSavegameHeader header;
+ header.write(saveFile, this, desc);
+
+ // Set up a serializer
+ Common::Serializer serializer(NULL, saveFile);
+
+ // Synchronise the data
+ synchronize(serializer);
+
+ saveFile->finalize();
+ delete saveFile;
+
+ return Common::kNoError;
+}
+
+void VoyeurEngine::synchronize(Common::Serializer &s) {
+ s.syncAsSint16LE(_glGoState);
+ s.syncAsSint16LE(_glGoStack);
+ s.syncAsSint16LE(_stampFlags);
+ s.syncAsSint16LE(_playStampGroupId);
+ s.syncAsSint16LE(_currentVocId);
+ s.syncAsSint16LE(_audioVideoId);
+
+ s.syncAsSint16LE(_iForceDeath);
+ s.syncAsSint16LE(_gameHour);
+ s.syncAsSint16LE(_gameMinute);
+ s.syncAsSint16LE(_flashTimeVal);
+ s.syncAsSint16LE(_flashTimeFlag);
+ s.syncAsSint16LE(_timeBarVal);
+ s.syncAsSint16LE(_checkPhoneVal);
+
+ // Sub-systems
+ _voy.synchronize(s);
+ _graphicsManager.synchronize(s);
+ _mainThread->synchronize(s);
+ _controlPtr->_state->synchronize(s);
+}
+
+/*------------------------------------------------------------------------*/
+
+bool VoyeurSavegameHeader::read(Common::InSaveFile *f) {
+ char id[4];
+ _thumbnail = NULL;
+
+ uint32 signature = f->readUint32BE();
+ if (signature != MKTAG('V', 'O', 'Y', 'R')) {
+ warning("Invalid savegame");
+ return false;
+ }
+
+ _version = f->readByte();
+ if (_version > VOYEUR_SAVEGAME_VERSION)
+ return false;
+
+ char c;
+ _saveName = "";
+ while ((c = f->readByte()) != 0)
+ _saveName += c;
+
+ // Get the thumbnail
+ _thumbnail = Graphics::loadThumbnail(*f);
+ if (!_thumbnail)
+ return false;
+
+ // Read in the save datet/ime
+ _saveYear = f->readSint16LE();
+ _saveMonth = f->readSint16LE();
+ _saveDay = f->readSint16LE();
+ _saveHour = f->readSint16LE();
+ _saveMinutes = f->readSint16LE();
+ _totalFrames = f->readUint32LE();
+
+ return true;
+}
+
+void VoyeurSavegameHeader::write(Common::OutSaveFile *f, VoyeurEngine *vm, const Common::String &saveName) {
+ // Write ident string
+ f->writeUint32BE(MKTAG('V', 'O', 'Y', 'R'));
+
+ // Write out savegame version
+ f->writeByte(VOYEUR_SAVEGAME_VERSION);
+
+ // Write out savegame name
+ f->write(saveName.c_str(), saveName.size());
+ f->writeByte(0);
+
+ // Create a thumbnail and save it
+ Graphics::Surface *thumb = new Graphics::Surface();
+ ::createThumbnail(thumb, (byte *)vm->_graphicsManager._screenSurface.getPixels(),
+ SCREEN_WIDTH, SCREEN_HEIGHT, vm->_graphicsManager._VGAColors);
+ Graphics::saveThumbnail(*f, *thumb);
+ thumb->free();
+ delete thumb;
+
+ // Write the save datet/ime
+ TimeDate td;
+ g_system->getTimeAndDate(td);
+ f->writeSint16LE(td.tm_year + 1900);
+ f->writeSint16LE(td.tm_mon + 1);
+ f->writeSint16LE(td.tm_mday);
+ f->writeSint16LE(td.tm_hour);
+ f->writeSint16LE(td.tm_min);
+ f->writeUint32LE(vm->_eventsManager.getGameCounter());
+}
+
+} // End of namespace Voyeur
diff --git a/engines/voyeur/voyeur.h b/engines/voyeur/voyeur.h
new file mode 100644
index 0000000000..4daa8f5372
--- /dev/null
+++ b/engines/voyeur/voyeur.h
@@ -0,0 +1,321 @@
+/* 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 VOYEUR_VOYEUR_H
+#define VOYEUR_VOYEUR_H
+
+#include "voyeur/debugger.h"
+#include "voyeur/data.h"
+#include "voyeur/events.h"
+#include "voyeur/files.h"
+#include "voyeur/graphics.h"
+#include "voyeur/sound.h"
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "common/error.h"
+#include "common/random.h"
+#include "common/savefile.h"
+#include "common/serializer.h"
+#include "common/util.h"
+#include "engines/engine.h"
+#include "graphics/surface.h"
+
+/**
+ * This is the namespace of the Voyeur engine.
+ *
+ * Status of this engine: In Development
+ *
+ * Games using this engine:
+ * - Voyeur
+ */
+namespace Voyeur {
+
+#define DEBUG_BASIC 1
+#define DEBUG_INTERMEDIATE 2
+#define DEBUG_DETAILED 3
+
+// Constants used for doInterface display of the mansion
+#define MANSION_MAX_X 784
+#define MANSION_MAX_Y 150
+#define MANSION_VIEW_X 40
+#define MANSION_VIEW_Y 27
+#define MANSION_VIEW_WIDTH 240
+#define MANSION_VIEW_HEIGHT 148
+#define MANSION_SCROLL_AREA_X 20
+#define MANSION_SCROLL_AREA_Y 20
+#define MANSION_SCROLL_INC_X 4
+#define MANSION_SCROLL_INC_Y 4
+
+enum VoyeurDebugChannels {
+ kDebugScripts = 1 << 0
+};
+
+enum VoyeurArea { AREA_NONE, AREA_APARTMENT, AREA_INTERFACE, AREA_ROOM, AREA_EVIDENCE };
+
+struct VoyeurGameDescription;
+
+class VoyeurEngine : public Engine {
+private:
+ const VoyeurGameDescription *_gameDescription;
+ Common::RandomSource _randomSource;
+ FontInfoResource _defaultFontInfo;
+
+ void ESP_Init();
+ void initializeManagers();
+ void globalInitBolt();
+ void initBolt();
+ void vInitInterrupts();
+ void initInput();
+
+ bool doHeadTitle();
+ void showConversionScreen();
+ bool doLock();
+ void showTitleScreen();
+ void doOpening();
+
+ void playStamp();
+ void initStamp();
+ void closeStamp();
+
+ /**
+ * Shows the game ending title animation
+ */
+ void doTailTitle();
+
+ /**
+ * Shows the game ending credits
+ */
+ void doClosingCredits();
+
+ /**
+ * Shows the final anti-piracy message before exiting the game
+ */
+ void doPiracy();
+
+ /**
+ * Review previous tape recordings on the TV
+ */
+ void reviewTape();
+
+ /**
+ * Shows the TV gossip animation
+ */
+ void doGossip();
+
+ /**
+ * Shows the animation of the VCR tape during the 'Call the Police' sequence
+ */
+ void doTapePlaying();
+
+ /**
+ * Does a check as to whether a murder has been witnessed
+ */
+ bool checkForMurder();
+
+ /**
+ * Does a check for whether incriminating evidence has been revealed
+ */
+ bool checkForIncriminate();
+
+ /**
+ * Plays a video event previously witnessed
+ */
+ void playAVideoEvent(int eventIndex);
+
+ /**
+ * Shows the user a screen to select one of four characters to send the
+ * video tape to
+ */
+ int getChooseButton();
+
+ /**
+ * Synchronizes the game data
+ */
+ void synchronize(Common::Serializer &s);
+protected:
+ // Engine APIs
+ virtual Common::Error run();
+ virtual bool hasFeature(EngineFeature f) const;
+public:
+ BoltFile *_bVoy;
+ Debugger _debugger;
+ EventsManager _eventsManager;
+ FilesManager _filesManager;
+ GraphicsManager _graphicsManager;
+ SoundManager _soundManager;
+ SVoy _voy;
+
+ BoltFile *_stampLibPtr;
+ BoltGroup *_controlGroupPtr;
+ ControlResource *_controlPtr;
+ byte *_stampData;
+ BoltGroup *_stackGroupPtr;
+ int _glGoState;
+ int _glGoStack;
+ int _stampFlags;
+ int _playStampGroupId;
+ int _currentVocId;
+
+ int _audioVideoId;
+ const int *_resolvePtr;
+ int _iForceDeath;
+ int _checkTransitionId;
+ int _gameHour;
+ int _gameMinute;
+ int _flashTimeVal;
+ bool _flashTimeFlag;
+ int _timeBarVal;
+ int _checkPhoneVal;
+ Common::Point _mansionViewPos;
+ ThreadResource *_mainThread;
+ VoyeurArea _voyeurArea;
+ int _loadGameSlot;
+public:
+ VoyeurEngine(OSystem *syst, const VoyeurGameDescription *gameDesc);
+ virtual ~VoyeurEngine();
+ void GUIError(const Common::String &msg);
+
+ uint32 getFeatures() const;
+ Common::Language getLanguage() const;
+ Common::Platform getPlatform() const;
+ uint16 getVersion() const;
+ bool getIsDemo() const;
+
+ int getRandomNumber(int maxNumber);
+ Common::String generateSaveName(int slotNumber);
+ virtual bool canLoadGameStateCurrently();
+ virtual bool canSaveGameStateCurrently();
+ virtual Common::Error loadGameState(int slot);
+ virtual Common::Error saveGameState(int slot, const Common::String &desc);
+ void loadGame(int slot);
+
+ void playRL2Video(const Common::String &filename);
+ void doTransitionCard(const Common::String &time, const Common::String &location);
+
+ /**
+ * Play a given video
+ */
+ void playAVideo(int videoId);
+
+ /**
+ * Play a given video for a given amount of time. This is particularly used
+ * for later tape playback, where it will only play back as much of the video
+ * as the user originally watched (since they can break out of watching a video).
+ */
+ void playAVideoDuration(int videoId, int duration);
+
+ /**
+ * Play an audio sequence
+ */
+ void playAudio(int audioId);
+
+ /**
+ * Saves the last time the game was played
+ */
+ void saveLastInplay();
+ void makeViewFinder();
+ void makeViewFinderP();
+ void initIFace();
+ void checkTransition();
+ int doComputerText(int maxLen);
+ void getComputerBrush();
+
+ /**
+ * Displays the time/charge remaining on the video camera screen
+ */
+ void doTimeBar(bool force);
+
+ /**
+ * If necessary, flashes the time remaining bar on the video camera screen
+ */
+ void flashTimeBar();
+
+ /**
+ * Handle scrolling of the mansion view in the camera sights
+ */
+ void doScroll(const Common::Point &pt);
+
+ /**
+ * Check for phone call
+ */
+ void checkPhoneCall();
+
+ /**
+ * Display evidence sequence from within a room
+ * Suspension of disbelief needed to believe that recording from a distance,
+ * you could still flip through the often pages of evidence for a single hotspot.
+ */
+ void doEvidDisplay(int evidId, int eventId);
+
+ /**
+ * Flips the active page and waits until it's drawn
+ */
+ void flipPageAndWait();
+
+ /**
+ * Flips the active page and waits until it's drawn and faded in
+ */
+ void flipPageAndWaitForFade();
+
+ /**
+ * Returns the string for the current in-game day of the week
+ */
+ Common::String getDayName();
+
+ /**
+ * Returns the string for the current in-game time of day
+ */
+ Common::String getTimeOfDay();
+
+ /**
+ * Show the ending sequence of the arrest
+ */
+ void showEndingNews();
+};
+
+#define VOYEUR_SAVEGAME_VERSION 1
+
+/**
+ * Header for Voyeur savegame files
+ */
+struct VoyeurSavegameHeader {
+ uint8 _version;
+ Common::String _saveName;
+ Graphics::Surface *_thumbnail;
+ int _saveYear, _saveMonth, _saveDay;
+ int _saveHour, _saveMinutes;
+ int _totalFrames;
+
+ /**
+ * Read in the header from the specified file
+ */
+ bool read(Common::InSaveFile *f);
+
+ /**
+ * Write out header information to the specified file
+ */
+ void write(Common::OutSaveFile *f, VoyeurEngine *vm, const Common::String &saveName);
+};
+
+} // End of namespace Voyeur
+
+#endif /* VOYEUR_VOYEUR_H */
diff --git a/engines/voyeur/voyeur_game.cpp b/engines/voyeur/voyeur_game.cpp
new file mode 100644
index 0000000000..b664074b56
--- /dev/null
+++ b/engines/voyeur/voyeur_game.cpp
@@ -0,0 +1,1423 @@
+/* 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 "voyeur/voyeur.h"
+#include "voyeur/staticres.h"
+#include "voyeur/animation.h"
+
+namespace Voyeur {
+
+void VoyeurEngine::playStamp() {
+ _stampLibPtr = NULL;
+ _filesManager.openBoltLib("stampblt.blt", _stampLibPtr);
+
+ _stampLibPtr->getBoltGroup(0);
+ _controlPtr->_state = _stampLibPtr->boltEntry(_controlPtr->_stateId >> 16)._stateResource;
+ assert(_controlPtr->_state);
+
+ _resolvePtr = &RESOLVE_TABLE[0];
+ initStamp();
+
+ PtrResource *threadsList = _stampLibPtr->boltEntry(3)._ptrResource;
+ _mainThread = threadsList->_entries[0]->_threadResource;
+ _mainThread->initThreadStruct(0, 0);
+
+ _voy._isAM = false;
+ _gameHour = 9;
+ _gameMinute = 0;
+ _voy._abortInterface = true;
+
+ int buttonId;
+ bool breakFlag = false;
+ while (!breakFlag && !shouldQuit()) {
+ _voyeurArea = AREA_NONE;
+ _eventsManager.getMouseInfo();
+ _playStampGroupId = _currentVocId = -1;
+ _audioVideoId = -1;
+
+ _mainThread->parsePlayCommands();
+
+ bool flag = breakFlag = (_voy._eventFlags & EVTFLAG_2) != 0;
+
+ switch (_voy._playStampMode) {
+ case 5:
+ buttonId = _mainThread->doInterface();
+
+ if (buttonId == -2) {
+ switch (_mainThread->doApt()) {
+ case 0:
+ _voy._aptLoadMode = 140;
+ break;
+ case 1:
+ _voy._eventFlags &= ~EVTFLAG_TIME_DISABLED;
+ _voy._abortInterface = true;
+ _mainThread->chooseSTAMPButton(22);
+ _voy._aptLoadMode = 143;
+ break;
+ case 2:
+ _voy._eventFlags &= ~EVTFLAG_TIME_DISABLED;
+ reviewTape();
+ _voy._abortInterface = true;
+ _voy._aptLoadMode = 142;
+ break;
+ case 3:
+ _voy._eventFlags &= ~EVTFLAG_TIME_DISABLED;
+ _mainThread->chooseSTAMPButton(21);
+ break;
+ case 4:
+ breakFlag = true;
+ break;
+ case 5:
+ doGossip();
+ _voy._abortInterface = true;
+ _voy._aptLoadMode = 141;
+ _voy._eventFlags &= ~EVTFLAG_100;
+ break;
+ default:
+ break;
+ }
+ } else {
+ _mainThread->chooseSTAMPButton(buttonId);
+ }
+
+ flag = true;
+ break;
+
+ case 6:
+ _mainThread->doRoom();
+ flag = true;
+ break;
+
+ case 16:
+ _voy._transitionId = 17;
+ buttonId = _mainThread->doApt();
+
+ switch (buttonId) {
+ case 1:
+ _mainThread->chooseSTAMPButton(22);
+ flag = true;
+ break;
+ case 2:
+ reviewTape();
+ _voy._abortInterface = true;
+ break;
+ case 4:
+ flag = true;
+ breakFlag = true;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case 17:
+ // Called the police, showing the tape
+ doTapePlaying();
+ if (!checkForMurder() && _voy._transitionId <= 15)
+ checkForIncriminate();
+
+ if (_voy._videoEventId != -1) {
+ // Show the found video that is of interest to the police
+ playAVideoEvent(_voy._videoEventId);
+ _voy._eventFlags &= ~EVTFLAG_RECORDING;
+ }
+
+ // Handle response
+ _mainThread->chooseSTAMPButton(0);
+ flag = true;
+ break;
+
+ case 130: {
+ // user selected to send the tape
+ if (_bVoy->getBoltGroup(_playStampGroupId)) {
+ _graphicsManager._backgroundPage = _bVoy->boltEntry(_playStampGroupId)._picResource;
+ _graphicsManager._backColors = _bVoy->boltEntry(_playStampGroupId + 1)._cMapResource;
+
+ buttonId = getChooseButton();
+ if (_eventsManager._rightClick)
+ // Aborted out of selecting a recipient
+ buttonId = 4;
+
+ _bVoy->freeBoltGroup(_playStampGroupId);
+ _graphicsManager.screenReset();
+ _playStampGroupId = -1;
+ flag = true;
+
+ if (buttonId != 4) {
+ _voy._playStampMode = 131;
+ _voy.checkForKey();
+ _mainThread->chooseSTAMPButton(buttonId);
+ } else {
+ _mainThread->chooseSTAMPButton(buttonId);
+ _voy._abortInterface = true;
+ }
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ do {
+ if (flag) {
+ if (_currentVocId != -1) {
+ _soundManager.stopVOCPlay();
+ _currentVocId = -1;
+ }
+
+ _audioVideoId = -1;
+
+ if (_voy._boltGroupId2 != -1) {
+ _bVoy->freeBoltGroup(_voy._boltGroupId2);
+ _voy._boltGroupId2 = -1;
+ }
+
+ if (_playStampGroupId != -1) {
+ _bVoy->freeBoltGroup(_playStampGroupId);
+ _playStampGroupId = -1;
+ }
+
+ // Break out of loop
+ flag = false;
+
+ } else if (_mainThread->_stateFlags & 2) {
+ _eventsManager.getMouseInfo();
+ _mainThread->chooseSTAMPButton(0);
+ flag = true;
+ } else {
+ _mainThread->chooseSTAMPButton(0);
+ flag = true;
+ }
+ } while (flag);
+ }
+
+ _voy._viewBounds = nullptr;
+ closeStamp();
+ _stampLibPtr->freeBoltGroup(0);
+ delete _stampLibPtr;
+}
+
+void VoyeurEngine::initStamp() {
+ _stampFlags &= ~1;
+ _stackGroupPtr = _controlGroupPtr;
+
+ if (!_controlPtr->_entries[0])
+ error("No control entries");
+
+ ThreadResource::initUseCount();
+}
+
+void VoyeurEngine::closeStamp() {
+ ThreadResource::unloadAllStacks(this);
+}
+
+void VoyeurEngine::doTailTitle() {
+ (*_graphicsManager._vPort)->setupViewPort(NULL);
+ _graphicsManager.screenReset();
+
+ if (_bVoy->getBoltGroup(0x600)) {
+ RL2Decoder decoder;
+ decoder.loadFile("a1100200.rl2");
+ decoder.start();
+ decoder.play(this);
+
+ if (!shouldQuit() && !_eventsManager._mouseClicked) {
+ doClosingCredits();
+
+ if (!shouldQuit() && !_eventsManager._mouseClicked) {
+ _graphicsManager.screenReset();
+
+ PictureResource *pic = _bVoy->boltEntry(0x602)._picResource;
+ CMapResource *pal = _bVoy->boltEntry(0x603)._cMapResource;
+
+ (*_graphicsManager._vPort)->setupViewPort(pic);
+ pal->startFade();
+ flipPageAndWaitForFade();
+ _eventsManager.delayClick(300);
+
+ pic = _bVoy->boltEntry(0x604)._picResource;
+ pal = _bVoy->boltEntry(0x605)._cMapResource;
+
+ (*_graphicsManager._vPort)->setupViewPort(pic);
+ pal->startFade();
+ flipPageAndWaitForFade();
+ _eventsManager.delayClick(120);
+
+ _soundManager.stopVOCPlay();
+ }
+ }
+
+ _bVoy->freeBoltGroup(0x600);
+ }
+
+ if (!shouldQuit()) {
+ _bVoy->getBoltGroup(0x100);
+ doPiracy();
+ }
+}
+
+void VoyeurEngine::doClosingCredits() {
+ if (!_bVoy->getBoltGroup(0x400))
+ return;
+
+ const char *msg = (const char *)_bVoy->memberAddr(0x404);
+ const byte *creditList = (const byte *)_bVoy->memberAddr(0x405);
+
+ (*_graphicsManager._vPort)->setupViewPort(NULL);
+ _graphicsManager.setColor(1, 180, 180, 180);
+ _graphicsManager.setColor(2, 200, 200, 200);
+ _eventsManager._intPtr._hasPalette = true;
+
+ _graphicsManager._fontPtr->_curFont = _bVoy->boltEntry(0x402)._fontResource;
+ _graphicsManager._fontPtr->_foreColor = 2;
+ _graphicsManager._fontPtr->_backColor = 2;
+ _graphicsManager._fontPtr->_fontSaveBack = false;
+ _graphicsManager._fontPtr->_fontFlags = 0;
+
+ _soundManager.startVOCPlay(152);
+ FontInfoResource &fi = *_graphicsManager._fontPtr;
+
+ for (int idx = 0; idx < 78; ++idx) {
+ const byte *entry = creditList + idx * 6;
+ int flags = READ_LE_UINT16(entry + 4);
+
+ if (flags & 0x10)
+ (*_graphicsManager._vPort)->fillPic();
+
+ if (flags & 1) {
+ fi._foreColor = 1;
+ fi._curFont = _bVoy->boltEntry(0x402)._fontResource;
+ fi._justify = ALIGN_CENTRE;
+ fi._justifyWidth = 384;
+ fi._justifyHeight = 240;
+ fi._pos = Common::Point(0, READ_LE_UINT16(entry));
+
+ (*_graphicsManager._vPort)->drawText(msg);
+ msg += strlen(msg) + 1;
+ }
+
+ if (flags & 0x40) {
+ fi._foreColor = 2;
+ fi._curFont = _bVoy->boltEntry(0x400)._fontResource;
+ fi._justify = ALIGN_CENTRE;
+ fi._justifyWidth = 384;
+ fi._justifyHeight = 240;
+ fi._pos = Common::Point(0, READ_LE_UINT16(entry));
+
+ (*_graphicsManager._vPort)->drawText(msg);
+ msg += strlen(msg) + 1;
+ }
+
+ if (flags & 2) {
+ fi._foreColor = 1;
+ fi._curFont = _bVoy->boltEntry(0x400)._fontResource;
+ fi._justify = ALIGN_LEFT;
+ fi._justifyWidth = 384;
+ fi._justifyHeight = 240;
+ fi._pos = Common::Point(38, READ_LE_UINT16(entry));
+
+ (*_graphicsManager._vPort)->drawText(msg);
+ msg += strlen(msg) + 1;
+
+ fi._foreColor = 2;
+ fi._justify = ALIGN_LEFT;
+ fi._justifyWidth = 384;
+ fi._justifyHeight = 240;
+ fi._pos = Common::Point(198, READ_LE_UINT16(entry));
+
+ (*_graphicsManager._vPort)->drawText(msg);
+ msg += strlen(msg) + 1;
+ }
+
+ if (flags & 4) {
+ fi._foreColor = 1;
+ fi._curFont = _bVoy->boltEntry(0x402)._fontResource;
+ fi._justify = ALIGN_CENTRE;
+ fi._justifyWidth = 384;
+ fi._justifyHeight = 240;
+ fi._pos = Common::Point(0, READ_LE_UINT16(entry));
+
+ (*_graphicsManager._vPort)->drawText(msg);
+ msg += strlen(msg) + 1;
+
+ fi._foreColor = 2;
+ fi._curFont = _bVoy->boltEntry(0x400)._fontResource;
+ fi._justify = ALIGN_CENTRE;
+ fi._justifyWidth = 384;
+ fi._justifyHeight = 240;
+ fi._pos = Common::Point(0, READ_LE_UINT16(entry) + 13);
+
+ (*_graphicsManager._vPort)->drawText(msg);
+ msg += strlen(msg) + 1;
+ }
+
+ if (flags & 0x20) {
+ flipPageAndWait();
+ _eventsManager.delayClick(READ_LE_UINT16(entry + 2) * 60);
+ }
+
+ if (shouldQuit() || _eventsManager._mouseClicked)
+ break;
+ }
+
+ _soundManager.stopVOCPlay();
+ _graphicsManager._fontPtr->_curFont = _bVoy->boltEntry(0x101)._fontResource;
+ _bVoy->freeBoltGroup(0x400);
+}
+
+void VoyeurEngine::doPiracy() {
+ _graphicsManager.screenReset();
+ _graphicsManager.setColor(1, 0, 0, 0);
+ _graphicsManager.setColor(2, 255, 255, 255);
+ _eventsManager._intPtr._hasPalette = true;
+ (*_graphicsManager._vPort)->setupViewPort(NULL);
+ (*_graphicsManager._vPort)->fillPic(1);
+
+ FontInfoResource &fi = *_graphicsManager._fontPtr;
+ fi._curFont = _bVoy->boltEntry(0x101)._fontResource;
+ fi._foreColor = 2;
+ fi._backColor = 2;
+ fi._fontSaveBack = false;
+ fi._fontFlags = 0;
+ fi._justify = ALIGN_CENTRE;
+ fi._justifyWidth = 384;
+ fi._justifyHeight = 230;
+
+ // Loop through the piracy message array to draw each line
+ int yp, idx;
+ for (idx = 0, yp = 33; idx < 10; ++idx) {
+ fi._pos = Common::Point(0, yp);
+ (*_graphicsManager._vPort)->drawText(PIRACY_MESSAGE[idx]);
+
+ yp += fi._curFont->_fontHeight + 4;
+ }
+
+ flipPageAndWait();
+ _eventsManager.getMouseInfo();
+ _eventsManager.delayClick(720);
+}
+
+void VoyeurEngine::reviewTape() {
+ int eventStart = 0;
+ int newX = -1;
+ int newY = -1;
+ int eventLine = 7;
+ Common::Rect tempRect(58, 30, 58 + 223, 30 + 124);
+ Common::Point pt;
+ int foundIndex;
+ int eventNum;
+
+ _bVoy->getBoltGroup(0x900);
+ PictureResource *cursor = _bVoy->boltEntry(0x903)._picResource;
+
+ if ((_voy._eventCount - 8) != 0)
+ eventStart = MAX(_voy._eventCount - 8, 0);
+
+ if ((eventStart + _voy._eventCount) <= 7)
+ eventLine = eventStart + _voy._eventCount - 1;
+
+ bool breakFlag = false;
+ while (!shouldQuit() && !breakFlag) {
+ _voy._viewBounds = _bVoy->boltEntry(0x907)._rectResource;
+ Common::Array<RectEntry> &hotspots = _bVoy->boltEntry(0x906)._rectResource->_entries;
+
+ _graphicsManager._backColors = _bVoy->boltEntry(0x902)._cMapResource;
+ _graphicsManager._backgroundPage = _bVoy->boltEntry(0x901)._picResource;
+ (*_graphicsManager._vPort)->setupViewPort(_graphicsManager._backgroundPage);
+ _graphicsManager._backColors->startFade();
+
+ flipPageAndWaitForFade();
+
+ _graphicsManager.setColor(1, 32, 32, 32);
+ _graphicsManager.setColor(2, 96, 96, 96);
+ _graphicsManager.setColor(3, 160, 160, 160);
+ _graphicsManager.setColor(4, 224, 224, 224);
+ _graphicsManager.setColor(9, 24, 64, 24);
+ _graphicsManager.setColor(10, 64, 132, 64);
+ _graphicsManager.setColor(11, 100, 192, 100);
+ _graphicsManager.setColor(12, 120, 248, 120);
+ _eventsManager.setCursorColor(128, 1);
+
+ _eventsManager._intPtr._hasPalette = true;
+ _graphicsManager._fontPtr->_curFont = _bVoy->boltEntry(0x909)._fontResource;
+ _graphicsManager._fontPtr->_fontSaveBack = false;
+ _graphicsManager._fontPtr->_fontFlags = 0;
+
+ _eventsManager.getMouseInfo();
+ if (newX == -1) {
+ _eventsManager.setMousePos(Common::Point(hotspots[1].left + 12, hotspots[1].top + 6));
+ } else {
+ _eventsManager.setMousePos(Common::Point(newX, newY));
+ }
+
+ _currentVocId = 151;
+ _voy._vocSecondsOffset = 0;
+ bool var1E = true;
+ do {
+ if (_currentVocId != -1 && !_soundManager.getVOCStatus()) {
+ _voy._musicStartTime = _voy._RTVNum;
+ _soundManager.startVOCPlay(_currentVocId);
+ }
+
+ if (var1E) {
+ var1E = false;
+ flipPageAndWait();
+
+ _graphicsManager._drawPtr->_penColor = 0;
+ _graphicsManager._drawPtr->_pos = Common::Point(tempRect.left, tempRect.top);
+ _graphicsManager._backgroundPage->sFillBox(tempRect.width(), tempRect.height());
+
+ int yp = 45;
+ int eventNum = eventStart;
+ for (int lineNum = 0; lineNum < 8 && eventNum < _voy._eventCount; ++lineNum, ++eventNum) {
+ _graphicsManager._fontPtr->_picFlags = 0;
+ _graphicsManager._fontPtr->_picSelect = 0xff;
+ _graphicsManager._fontPtr->_picPick = 7;
+ _graphicsManager._fontPtr->_picOnOff = (lineNum == eventLine) ? 8 : 0;
+ _graphicsManager._fontPtr->_pos = Common::Point(68, yp);
+ _graphicsManager._fontPtr->_justify = ALIGN_LEFT;
+ _graphicsManager._fontPtr->_justifyWidth = 0;
+ _graphicsManager._fontPtr->_justifyHeight = 0;
+
+ Common::String msg = _eventsManager.getEvidString(eventNum);
+ _graphicsManager._backgroundPage->drawText(msg);
+
+ yp += 15;
+ }
+
+ (*_graphicsManager._vPort)->addSaveRect(
+ (*_graphicsManager._vPort)->_lastPage, tempRect);
+ flipPageAndWait();
+
+ (*_graphicsManager._vPort)->addSaveRect(
+ (*_graphicsManager._vPort)->_lastPage, tempRect);
+ }
+
+ _graphicsManager.sDrawPic(cursor, *_graphicsManager._vPort,
+ _eventsManager.getMousePos());
+ flipPageAndWait();
+
+ _eventsManager.getMouseInfo();
+ foundIndex = -1;
+
+ Common::Point tempPos = _eventsManager.getMousePos() + Common::Point(14, 7);
+ for (uint idx = 0; idx < hotspots.size(); ++idx) {
+ if (hotspots[idx].contains(tempPos)) {
+ // Found hotspot area
+ foundIndex = idx;
+ break;
+ }
+ }
+
+ pt = _eventsManager.getMousePos();
+ if (tempPos.x >= 68 && tempPos.x <= 277 && tempPos.y >= 31 && tempPos.y <= 154) {
+ tempPos.y -= 2;
+ foundIndex = (tempPos.y - 31) / 15;
+ if ((tempPos.y - 31) % 15 >= 12 || (eventStart + foundIndex) >= _voy._eventCount) {
+ _eventsManager.setCursorColor(128, 0);
+ foundIndex = 999;
+ } else if (!_eventsManager._leftClick) {
+ _eventsManager.setCursorColor(128, 2);
+ foundIndex = -1;
+ } else {
+ _eventsManager.setCursorColor(128, 2);
+ eventLine = foundIndex;
+
+ flipPageAndWait();
+
+ _graphicsManager._drawPtr->_penColor = 0;
+ _graphicsManager._drawPtr->_pos = Common::Point(tempRect.left, tempRect.top);
+ _graphicsManager._backgroundPage->sFillBox(tempRect.width(), tempRect.height());
+
+ int yp = 45;
+ eventNum = eventStart;
+ for (int idx = 0; idx < 8 && eventNum < _voy._eventCount; ++idx, ++eventNum) {
+ _graphicsManager._fontPtr->_picFlags = 0;
+ _graphicsManager._fontPtr->_picSelect = 0xff;
+ _graphicsManager._fontPtr->_picPick = 7;
+ _graphicsManager._fontPtr->_picOnOff = (idx == eventLine) ? 8 : 0;
+ _graphicsManager._fontPtr->_pos = Common::Point(68, yp);
+ _graphicsManager._fontPtr->_justify = ALIGN_LEFT;
+ _graphicsManager._fontPtr->_justifyWidth = 0;
+ _graphicsManager._fontPtr->_justifyHeight = 0;
+
+ Common::String msg = _eventsManager.getEvidString(eventNum);
+ _graphicsManager._backgroundPage->drawText(msg);
+
+ yp += 15;
+ }
+
+ (*_graphicsManager._vPort)->addSaveRect(
+ (*_graphicsManager._vPort)->_lastPage, tempRect);
+ flipPageAndWait();
+
+ (*_graphicsManager._vPort)->addSaveRect(
+ (*_graphicsManager._vPort)->_lastPage, tempRect);
+ flipPageAndWait();
+
+ _eventsManager.getMouseInfo();
+ foundIndex = -1;
+ }
+ } else if ((_voy._eventFlags & EVTFLAG_40) && _voy._viewBounds->left == pt.x &&
+ _voy._viewBounds->bottom == pt.y) {
+ foundIndex = 999;
+ } else if ((_voy._eventFlags & EVTFLAG_40) && _voy._viewBounds->left == pt.x &&
+ _voy._viewBounds->top == pt.y) {
+ foundIndex = 998;
+ } else {
+ _eventsManager.setCursorColor(128, (foundIndex == -1) ? 0 : 1);
+ }
+
+ _eventsManager._intPtr._hasPalette = true;
+
+ if (_eventsManager._mouseClicked || _eventsManager._mouseUnk) {
+ switch (foundIndex) {
+ case 2:
+ if (eventStart > 0) {
+ --eventStart;
+ var1E = true;
+ }
+ foundIndex = -1;
+ break;
+
+ case 3:
+ if (eventStart > 0) {
+ eventStart -= 8;
+ if (eventStart < 0)
+ eventStart = 0;
+ var1E = true;
+ }
+ foundIndex = -1;
+ break;
+
+ case 4:
+ if ((_voy._eventCount - 8) > eventStart) {
+ ++eventStart;
+ var1E = true;
+ }
+ foundIndex = -1;
+ break;
+
+ case 5:
+ if (_voy._eventCount >= 8 && (_voy._eventCount - 8) != eventStart) {
+ eventStart += 8;
+ if ((_voy._eventCount - 8) < eventStart)
+ eventStart = _voy._eventCount - 8;
+ var1E = true;
+ }
+ foundIndex = -1;
+ break;
+
+ default:
+ break;
+ }
+
+ while (eventLine > 0 && (eventLine + eventStart) >= _voy._eventCount)
+ --eventLine;
+ }
+
+ pt = _eventsManager.getMousePos();
+ if (_eventsManager._mouseClicked && _voy._viewBounds->left == pt.x &&
+ (_voy._eventFlags & EVTFLAG_40) && _eventsManager._rightClick) {
+ _controlPtr->_state->_victimIndex = (pt.y / 60) + 1;
+ foundIndex = -1;
+ _eventsManager._rightClick = 0;
+ }
+
+ if (_eventsManager._rightClick)
+ foundIndex = 0;
+
+ } while (!shouldQuit() && (!_eventsManager._mouseClicked || foundIndex == -1));
+
+ newY = _eventsManager.getMousePos().y;
+ _voy._fadingType = 0;
+ _voy._viewBounds = nullptr;
+ (*_graphicsManager._vPort)->setupViewPort(NULL);
+
+ if (_currentVocId != -1) {
+ _voy._vocSecondsOffset = _voy._RTVNum - _voy._musicStartTime;
+ _soundManager.stopVOCPlay();
+ }
+
+ // Break out if the exit button was pressed
+ if (!foundIndex)
+ break;
+
+ int eventIndex = eventStart + eventLine;
+ VoyeurEvent &e = _voy._events[eventIndex];
+ switch (e._type) {
+ case EVTYPE_VIDEO:
+ playAVideoEvent(eventIndex);
+ break;
+
+ case EVTYPE_AUDIO: {
+ _audioVideoId = e._audioVideoId;
+ _voy._vocSecondsOffset = e._computerOn;
+
+ _bVoy->getBoltGroup(0x7F00);
+ _graphicsManager._backgroundPage = _bVoy->boltEntry(0x7F00 +
+ BLIND_TABLE[_audioVideoId])._picResource;
+ _graphicsManager._backColors = _bVoy->boltEntry(0x7F01 +
+ BLIND_TABLE[_audioVideoId])._cMapResource;
+
+ (*_graphicsManager._vPort)->setupViewPort(_graphicsManager._backgroundPage);
+ _graphicsManager._backColors->startFade();
+ flipPageAndWaitForFade();
+
+ _eventsManager._intPtr._flashStep = 1;
+ _eventsManager._intPtr._flashTimer = 0;
+ _voy._eventFlags &= ~EVTFLAG_TIME_DISABLED;
+
+ // Play suond for the given duration
+ _soundManager.setVOCOffset(_voy._vocSecondsOffset);
+ _soundManager.startVOCPlay(_audioVideoId + 159);
+ uint32 secondsDuration = e._computerOff;
+
+ _eventsManager.getMouseInfo();
+ while (!_eventsManager._mouseClicked && _soundManager.getVOCStatus() &&
+ _soundManager.getVOCFrame() < secondsDuration) {
+ _eventsManager.getMouseInfo();
+ _eventsManager.delay(10);
+ }
+
+ _voy._eventFlags |= EVTFLAG_TIME_DISABLED;
+ _soundManager.stopVOCPlay();
+ _bVoy->freeBoltGroup(0x7F00);
+ break;
+ }
+
+ case EVTYPE_EVID:
+ _voy.reviewAnEvidEvent(eventIndex);
+
+ _voy._vocSecondsOffset = _voy._RTVNum - _voy._musicStartTime;
+ _soundManager.stopVOCPlay();
+ _bVoy->getBoltGroup(0x900);
+ break;
+
+ case EVTYPE_COMPUTER:
+ _voy.reviewComputerEvent(eventIndex);
+
+ _voy._vocSecondsOffset = _voy._RTVNum - _voy._musicStartTime;
+ _soundManager.stopVOCPlay();
+ _bVoy->getBoltGroup(0x900);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ _graphicsManager._fontPtr->_curFont = _bVoy->boltEntry(0x101)._fontResource;
+
+ (*_graphicsManager._vPort)->fillPic(0);
+ flipPageAndWait();
+ _bVoy->freeBoltGroup(0x900);
+}
+
+void VoyeurEngine::doGossip() {
+ _graphicsManager.resetPalette();
+ _graphicsManager.screenReset();
+
+ if (!_bVoy->getBoltGroup(0x300))
+ return;
+
+ // Load the gossip animation
+ RL2Decoder decoder;
+ decoder.loadFile("a2050100.rl2", false);
+ decoder.start();
+
+ // Get the resource data for the first gossip video
+ PictureResource *bgPic = _bVoy->boltEntry(0x300)._picResource;
+ CMapResource *pal = _bVoy->boltEntry(0x301)._cMapResource;
+ pal->startFade();
+
+ // Transfer initial background to video decoder
+ PictureResource videoFrame(decoder.getVideoTrack()->getBackSurface());
+ bgPic->_bounds.moveTo(0, 0);
+ _graphicsManager.sDrawPic(bgPic, &videoFrame, Common::Point(0, 0));
+
+ byte *frameNumsP = _bVoy->memberAddr(0x309);
+ byte *posP = _bVoy->boltEntry(0x30A)._data;
+
+ // Play the initial gossip video
+ decoder.play(this, 0x302, frameNumsP, posP);
+ decoder.close();
+
+ // Reset the palette and clear the screen
+ _graphicsManager.resetPalette();
+ _graphicsManager.screenReset();
+
+ // Play interview video
+ RL2Decoder decoder2;
+ decoder2.loadFile("a2110100.rl2", true);
+ decoder2.start();
+
+ _eventsManager.getMouseInfo();
+ decoder2.play(this);
+ decoder2.close();
+
+ _bVoy->freeBoltGroup(0x300);
+ _graphicsManager.screenReset();
+}
+
+void VoyeurEngine::doTapePlaying() {
+ if (!_bVoy->getBoltGroup(0xA00))
+ return;
+
+ _eventsManager.getMouseInfo();
+ _graphicsManager._backColors = _bVoy->boltEntry(0xA01)._cMapResource;
+ _graphicsManager._backgroundPage = _bVoy->boltEntry(0xA00)._picResource;
+ PictureResource *pic = _bVoy->boltEntry(0xA02)._picResource;
+ VInitCycleResource *cycle = _bVoy->boltEntry(0xA05)._vInitCycleResource;
+
+ (*_graphicsManager._vPort)->setupViewPort(_graphicsManager._backgroundPage);
+ _graphicsManager.sDrawPic(pic, *_graphicsManager._vPort, Common::Point(57, 30));
+ _graphicsManager._backColors->startFade();
+ flipPageAndWaitForFade();
+
+ cycle->vStartCycle();
+
+ _soundManager.startVOCPlay("vcr.voc");
+ while (!shouldQuit() && !_eventsManager._mouseClicked && _soundManager.getVOCStatus()) {
+ _eventsManager.delayClick(2);
+ }
+
+ _soundManager.stopVOCPlay();
+ cycle->vStopCycle();
+ _bVoy->freeBoltGroup(0xA00);
+}
+
+bool VoyeurEngine::checkForMurder() {
+ int v = _controlPtr->_state->_victimMurderIndex;
+
+ for (int idx = 0; idx < _voy._eventCount; ++idx) {
+ VoyeurEvent &evt = _voy._events[idx];
+
+ if (evt._type == EVTYPE_VIDEO) {
+ switch (_controlPtr->_state->_victimIndex) {
+ case 1:
+ if (evt._audioVideoId == 41 && evt._computerOn <= 15 &&
+ (evt._computerOff + evt._computerOn) >= 16) {
+ _controlPtr->_state->_victimMurderIndex = 1;
+ }
+ break;
+
+ case 2:
+ if (evt._audioVideoId == 53 && evt._computerOn <= 19 &&
+ (evt._computerOff + evt._computerOn) >= 21) {
+ _controlPtr->_state->_victimMurderIndex = 2;
+ }
+ break;
+
+ case 3:
+ if (evt._audioVideoId == 50 && evt._computerOn <= 28 &&
+ (evt._computerOff + evt._computerOn) >= 29) {
+ _controlPtr->_state->_victimMurderIndex = 3;
+ }
+ break;
+
+ case 4:
+ if (evt._audioVideoId == 43 && evt._computerOn <= 10 &&
+ (evt._computerOff + evt._computerOn) >= 14) {
+ _controlPtr->_state->_victimMurderIndex = 4;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (_controlPtr->_state->_victimMurderIndex == _controlPtr->_state->_victimIndex) {
+ _voy._videoEventId = idx;
+ return true;
+ }
+ }
+
+ _controlPtr->_state->_victimMurderIndex = v;
+ _voy._videoEventId = -1;
+ return false;
+}
+
+bool VoyeurEngine::checkForIncriminate() {
+ _voy._incriminatedVictimNumber = 0;
+
+ for (int idx = 0; idx < _voy._eventCount; ++idx) {
+ VoyeurEvent &evt = _voy._events[idx];
+
+ if (evt._type == EVTYPE_VIDEO) {
+ if (evt._audioVideoId == 44 && evt._computerOn <= 40 &&
+ (evt._computerOff + evt._computerOn) >= 70) {
+ _voy._incriminatedVictimNumber = 1;
+ }
+
+ if (evt._audioVideoId == 44 && evt._computerOn <= 79 &&
+ (evt._computerOff + evt._computerOn) >= 129) {
+ _voy._incriminatedVictimNumber = 1;
+ }
+
+ if (evt._audioVideoId == 20 && evt._computerOn <= 28 &&
+ (evt._computerOff + evt._computerOn) >= 45) {
+ _voy._incriminatedVictimNumber = 2;
+ }
+
+ if (evt._audioVideoId == 35 && evt._computerOn <= 17 &&
+ (evt._computerOff + evt._computerOn) >= 36) {
+ _voy._incriminatedVictimNumber = 3;
+ }
+
+ if (evt._audioVideoId == 30 && evt._computerOn <= 80 &&
+ (evt._computerOff + evt._computerOn) >= 139) {
+ _voy._incriminatedVictimNumber = 4;
+ }
+ }
+
+ if (_voy._incriminatedVictimNumber) {
+ _controlPtr->_state->_victimMurderIndex = 88;
+ _voy._videoEventId = idx;
+ return true;
+ }
+ }
+
+ _voy._videoEventId = -1;
+ return false;
+}
+
+void VoyeurEngine::playAVideoEvent(int eventIndex) {
+ VoyeurEvent &evt = _voy._events[eventIndex];
+ _audioVideoId = evt._audioVideoId;
+ _voy._vocSecondsOffset = evt._computerOn;
+ _eventsManager._videoDead = evt._dead;
+ _voy._eventFlags &= ~EVTFLAG_TIME_DISABLED;
+
+ playAVideoDuration(_audioVideoId, evt._computerOff);
+
+ _voy._eventFlags |= EVTFLAG_TIME_DISABLED;
+ if (_eventsManager._videoDead != -1) {
+ _bVoy->freeBoltGroup(0xE00);
+ _eventsManager._videoDead = -1;
+ flipPageAndWait();
+ _eventsManager._videoDead = -1;
+ }
+
+ _audioVideoId = -1;
+ if (_eventsManager._videoDead != -1) {
+ _bVoy->freeBoltGroup(0xE00);
+ _eventsManager._videoDead = -1;
+ flipPageAndWait();
+ }
+}
+
+int VoyeurEngine::getChooseButton() {
+ int prevIndex = -2;
+ Common::Array<RectEntry> &hotspots = _bVoy->boltEntry(_playStampGroupId
+ + 6)._rectResource->_entries;
+ int selectedIndex = -1;
+
+ (*_graphicsManager._vPort)->setupViewPort(_graphicsManager._backgroundPage);
+ _graphicsManager._backColors->_steps = 0;
+ _graphicsManager._backColors->startFade();
+ flipPageAndWait();
+
+ _voy._viewBounds = _bVoy->boltEntry(_playStampGroupId + 7)._rectResource;
+ PictureResource *cursorPic = _bVoy->boltEntry(_playStampGroupId + 2)._picResource;
+
+ do {
+ do {
+ if (_currentVocId != -1 && !_soundManager.getVOCStatus())
+ _soundManager.startVOCPlay(_currentVocId);
+
+ _eventsManager.getMouseInfo();
+ selectedIndex = -1;
+ Common::Point pt = _eventsManager.getMousePos();
+
+ for (uint idx = 0; idx < hotspots.size(); ++idx) {
+ if (hotspots[idx].contains(pt)) {
+ if (!_voy._victimMurdered || ((int)idx + 1) != _controlPtr->_state->_victimIndex) {
+ selectedIndex = idx;
+ if (selectedIndex != prevIndex) {
+ PictureResource *btnPic = _bVoy->boltEntry(_playStampGroupId + 8 + idx)._picResource;
+ _graphicsManager.sDrawPic(btnPic, *_graphicsManager._vPort,
+ Common::Point(106, 200));
+
+ cursorPic = _bVoy->boltEntry(_playStampGroupId + 4)._picResource;
+ }
+ }
+ }
+ }
+
+ if (selectedIndex == -1) {
+ cursorPic = _bVoy->boltEntry(_playStampGroupId + 2)._picResource;
+ PictureResource *btnPic = _bVoy->boltEntry(_playStampGroupId + 12)._picResource;
+ _graphicsManager.sDrawPic(btnPic, *_graphicsManager._vPort,
+ Common::Point(106, 200));
+ }
+
+ _graphicsManager.sDrawPic(cursorPic, *_graphicsManager._vPort,
+ Common::Point(pt.x + 13, pt.y - 12));
+
+ flipPageAndWait();
+ } while (!shouldQuit() && !_eventsManager._mouseClicked);
+ } while (!shouldQuit() && selectedIndex == -1 && !_eventsManager._rightClick);
+
+ return selectedIndex;
+}
+
+void VoyeurEngine::makeViewFinder() {
+ _graphicsManager._backgroundPage = _bVoy->boltEntry(0x103)._picResource;
+ _graphicsManager.sDrawPic(_graphicsManager._backgroundPage,
+ *_graphicsManager._vPort, Common::Point(0, 0));
+ CMapResource *pal = _bVoy->boltEntry(0x104)._cMapResource;
+
+ int palOffset = 0;
+ switch (_voy._transitionId) {
+ case 1:
+ case 2:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 17:
+ palOffset = 0;
+ break;
+ case 3:
+ palOffset = 1;
+ break;
+ case 4:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ case 16:
+ palOffset = 2;
+ break;
+ default:
+ break;
+ }
+
+ (*_graphicsManager._vPort)->drawIfaceTime();
+ doTimeBar(true);
+ pal->startFade();
+
+ flipPageAndWaitForFade();
+
+ _graphicsManager.setColor(241, 105, 105, 105);
+ _graphicsManager.setColor(242, 105, 105, 105);
+ _graphicsManager.setColor(243, 105, 105, 105);
+ _graphicsManager.setColor(palOffset + 241, 219, 235, 235);
+
+ _eventsManager._intPtr._hasPalette = true;
+}
+
+void VoyeurEngine::makeViewFinderP() {
+ _graphicsManager.screenReset();
+}
+
+void VoyeurEngine::initIFace() {
+ int playStamp1 = _playStampGroupId;
+ switch (_voy._transitionId) {
+ case 0:
+ break;
+ case 1:
+ case 2:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ _playStampGroupId = 0xB00;
+ break;
+ case 3:
+ _playStampGroupId = 0xC00;
+ break;
+ default:
+ _playStampGroupId = 0xD00;
+ break;
+ }
+ if (playStamp1 != -1)
+ _bVoy->freeBoltGroup(playStamp1, true);
+
+ _bVoy->getBoltGroup(_playStampGroupId);
+ CMapResource *pal = _bVoy->boltEntry(_playStampGroupId + 2)._cMapResource;
+ pal->startFade();
+
+ // Start the mansion off centered
+ _mansionViewPos = Common::Point((MANSION_MAX_X - MANSION_VIEW_WIDTH) / 2,
+ (MANSION_MAX_Y - MANSION_VIEW_HEIGHT) / 2);
+ doScroll(_mansionViewPos);
+
+ _voy._viewBounds = _bVoy->boltEntry(_playStampGroupId)._rectResource;
+
+ // Show the cursor using ScummVM functionality
+ _eventsManager.showCursor();
+
+ // Note: the original did two loops to preload members here, which is
+ // redundant for ScummVM, since computers are faster these days, and
+ // getting resources as needed will be fast enough.
+}
+
+void VoyeurEngine::doScroll(const Common::Point &pt) {
+ Common::Rect clipRect(72, 47, 72 + 240, 47 + 148);
+ (*_graphicsManager._vPort)->setupViewPort(NULL, &clipRect);
+
+ int base = 0;
+ switch (_voy._transitionId) {
+ case 0:
+ break;
+ case 1:
+ case 2:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ base = 0xB00;
+ break;
+ case 3:
+ base = 0xC00;
+ break;
+ default:
+ base = 0xD00;
+ }
+
+ if (base) {
+ PictureResource *pic = _bVoy->boltEntry(base + 3)._picResource;
+ _graphicsManager.sDrawPic(pic, *_graphicsManager._vPort, Common::Point(784 - pt.x - 712, 150 - pt.y - 104));
+ pic = _bVoy->boltEntry(base + 4)._picResource;
+ _graphicsManager.sDrawPic(pic, *_graphicsManager._vPort, Common::Point(784 - pt.x - 712, 150 - pt.y - 44));
+ pic = _bVoy->boltEntry(base + 5)._picResource;
+ _graphicsManager.sDrawPic(pic, *_graphicsManager._vPort, Common::Point(784 - pt.x - 712, 150 - pt.y + 16));
+ pic = _bVoy->boltEntry(base + 6)._picResource;
+ _graphicsManager.sDrawPic(pic, *_graphicsManager._vPort, Common::Point(784 - pt.x - 712, 150 - pt.y + 76));
+ pic = _bVoy->boltEntry(base + 7)._picResource;
+ _graphicsManager.sDrawPic(pic, *_graphicsManager._vPort, Common::Point(784 - pt.x - 712, 150 - pt.y + 136));
+ }
+
+ (*_graphicsManager._vPort)->setupViewPort(NULL);
+}
+
+void VoyeurEngine::checkTransition() {
+ Common::String time, day;
+
+ if (_voy._transitionId != _checkTransitionId) {
+ // Get the day
+ day = getDayName();
+
+ // Only proceed if a valid day string was returned
+ if (!day.empty()) {
+ _graphicsManager.fadeDownICF(6);
+
+ // Get the time of day string
+ time = getTimeOfDay();
+
+ // Show a transition card with the day and time, and wait
+ doTransitionCard(day, time);
+ _eventsManager.delayClick(180);
+ }
+
+ _checkTransitionId = _voy._transitionId;
+ }
+}
+
+Common::String VoyeurEngine::getDayName() {
+ switch (_voy._transitionId) {
+ case 0:
+ return "";
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ return SATURDAY;
+ case 17:
+ return MONDAY;
+ default:
+ return SUNDAY;
+ }
+}
+
+Common::String VoyeurEngine::getTimeOfDay() {
+ if (_voy._transitionId == 17)
+ return "";
+
+ return Common::String::format("%d:%02d%s", _gameHour, _gameMinute, _voy._isAM ? AM : PM);
+}
+
+int VoyeurEngine::doComputerText(int maxLen) {
+ FontInfoResource &font = *_graphicsManager._fontPtr;
+ int totalChars = 0;
+
+ font._curFont = _bVoy->boltEntry(0x4910)._fontResource;
+ font._foreColor = 129;
+ font._fontSaveBack = false;
+ font._fontFlags = 0;
+ if (_voy._vocSecondsOffset > 60)
+ _voy._vocSecondsOffset = 0;
+
+ if (_voy._RTVNum > _voy._computerTimeMax && maxLen == 9999) {
+ if (_currentVocId != -1)
+ _soundManager.startVOCPlay(_currentVocId);
+ font._justify = ALIGN_LEFT;
+ font._justifyWidth = 384;
+ font._justifyHeight = 100;
+ font._pos = Common::Point(128, 100);
+ (*_graphicsManager._vPort)->drawText(END_OF_MESSAGE);
+ } else if (_voy._RTVNum < _voy._computerTimeMin && maxLen == 9999) {
+ if (_currentVocId != -1)
+ _soundManager.startVOCPlay(_currentVocId);
+ font._justify = ALIGN_LEFT;
+ font._justifyWidth = 384;
+ font._justifyHeight = 100;
+ font._pos = Common::Point(120, 100);
+ (*_graphicsManager._vPort)->drawText(START_OF_MESSAGE);
+ } else {
+ char *msg = (char *)_bVoy->memberAddr(0x4900 + _voy._computerTextId);
+ font._pos = Common::Point(96, 60);
+
+ bool showEnd = true;
+ int yp = 60;
+ do {
+ if (_currentVocId != -1 && !_soundManager.getVOCStatus()) {
+ if (_voy._vocSecondsOffset > 60)
+ _voy._vocSecondsOffset = 0;
+ _soundManager.startVOCPlay(_currentVocId);
+ }
+
+ char c = *msg++;
+ if (c == '\0') {
+ if (showEnd) {
+ _eventsManager.delay(90);
+ _graphicsManager._drawPtr->_pos = Common::Point(96, 54);
+ _graphicsManager._drawPtr->_penColor = 254;
+ (*_graphicsManager._vPort)->sFillBox(196, 124);
+ _graphicsManager._fontPtr->_justify = ALIGN_LEFT;
+ _graphicsManager._fontPtr->_justifyWidth = 384;
+ _graphicsManager._fontPtr->_justifyHeight = 100;
+ _graphicsManager._fontPtr->_pos = Common::Point(128, 100);
+ (*_graphicsManager._vPort)->drawText(END_OF_MESSAGE);
+ }
+ break;
+ }
+
+ if (c == '~' || c == '^') {
+ if (c == '^') {
+ yp += 10;
+ } else {
+ _eventsManager.delay(90);
+ _graphicsManager._drawPtr->_pos = Common::Point(96, 54);
+ _graphicsManager._drawPtr->_penColor = 255;
+ (*_graphicsManager._vPort)->sFillBox(196, 124);
+ yp = 60;
+ }
+
+ _graphicsManager._fontPtr->_pos = Common::Point(96, yp);
+ } else if (c == '_') {
+ showEnd = false;
+ } else {
+ _graphicsManager._fontPtr->_justify = ALIGN_LEFT;
+ _graphicsManager._fontPtr->_justifyWidth = 0;
+ _graphicsManager._fontPtr->_justifyHeight = 0;
+ (*_graphicsManager._vPort)->drawText(Common::String(c));
+ _eventsManager.delay(4);
+ }
+
+ flipPageAndWait();
+ _eventsManager.getMouseInfo();
+ ++totalChars;
+
+ } while (!shouldQuit() && !_eventsManager._mouseClicked && totalChars < maxLen);
+
+ _voy._computerTimeMax = 0;
+ }
+
+ flipPageAndWait();
+
+ _graphicsManager._fontPtr->_curFont = _bVoy->boltEntry(0x101)._fontResource;
+ return totalChars;
+}
+
+void VoyeurEngine::getComputerBrush() {
+ if (_bVoy->getBoltGroup(0x4900)) {
+ PictureResource *pic = _bVoy->boltEntry(0x490E)._picResource;
+ int xp = (384 - pic->_bounds.width()) / 2;
+ int yp = (240 - pic->_bounds.height()) / 2 - 4;
+
+ (*_graphicsManager._vPort)->drawPicPerm(pic, Common::Point(xp, yp));
+
+ CMapResource *pal = _bVoy->boltEntry(0x490F)._cMapResource;
+ pal->startFade();
+ }
+}
+
+void VoyeurEngine::doTimeBar(bool force) {
+ flashTimeBar();
+
+ if ((force || _timeBarVal != _voy._RTVNum) && _voy._RTVLimit > 0) {
+ if (_voy._RTVNum > _voy._RTVLimit || _voy._RTVNum < 0)
+ _voy._RTVNum = _voy._RTVLimit - 1;
+
+ _timeBarVal = _voy._RTVNum;
+ int height = ((_voy._RTVLimit - _voy._RTVNum) * 59) / _voy._RTVLimit;
+ int fullHeight = MAX(151 - height, 93);
+
+ _graphicsManager._drawPtr->_penColor = 134;
+ _graphicsManager._drawPtr->_pos = Common::Point(39, 92);
+
+ (*_graphicsManager._vPort)->sFillBox(6, fullHeight - 92);
+ if (height > 0) {
+ _graphicsManager.setColor(215, 238, 238, 238);
+ _eventsManager._intPtr._hasPalette = true;
+
+ _graphicsManager._drawPtr->_penColor = 215;
+ _graphicsManager._drawPtr->_pos = Common::Point(39, fullHeight);
+ (*_graphicsManager._vPort)->sFillBox(6, height);
+ }
+ }
+}
+
+void VoyeurEngine::flashTimeBar() {
+ if (_voy._RTVNum >= 0 && (_voy._RTVLimit - _voy._RTVNum) < 11 &&
+ (_eventsManager._intPtr._flashTimer >= (_flashTimeVal + 15) ||
+ _eventsManager._intPtr._flashTimer < _flashTimeVal)) {
+ // Within camera low power range
+ _flashTimeVal = _eventsManager._intPtr._flashTimer;
+
+ if (_flashTimeFlag)
+ _graphicsManager.setColor(240, 220, 20, 20);
+ else
+ _graphicsManager.setColor(240, 220, 220, 220);
+
+ _eventsManager._intPtr._hasPalette = true;
+ _flashTimeFlag = !_flashTimeFlag;
+ }
+}
+
+void VoyeurEngine::checkPhoneCall() {
+ if ((_voy._RTVLimit - _voy._RTVNum) >= 36 && _voy._totalPhoneCalls < 5 &&
+ _currentVocId <= 151 && _currentVocId > 146) {
+ if ((_voy._switchBGNum < _checkPhoneVal || _checkPhoneVal > 180) &&
+ !_soundManager.getVOCStatus()) {
+ int soundIndex;
+ do {
+ soundIndex = getRandomNumber(4);
+ } while (_voy._phoneCallsReceived[soundIndex]);
+ _currentVocId = 154 + soundIndex;
+
+ _soundManager.stopVOCPlay();
+ _soundManager.startVOCPlay(_currentVocId);
+ _checkPhoneVal = _voy._switchBGNum;
+ ++_voy._phoneCallsReceived[soundIndex];
+ ++_voy._totalPhoneCalls;
+ }
+ }
+}
+
+void VoyeurEngine::doEvidDisplay(int evidId, int eventId) {
+ _eventsManager.getMouseInfo();
+ flipPageAndWait();
+
+ if (_currentVocId != -1) {
+ _voy._vocSecondsOffset = _voy._RTVNum - _voy._musicStartTime;
+ _soundManager.stopVOCPlay();
+ }
+
+ _bVoy->getBoltGroup(_voy._boltGroupId2);
+ PictureResource *pic = _bVoy->boltEntry(_voy._boltGroupId2 + evidId * 2)._picResource;
+ _graphicsManager.sDrawPic(pic, *_graphicsManager._vPort, Common::Point(
+ (384 - pic->_bounds.width()) / 2, (240 - pic->_bounds.height()) / 2));
+ _bVoy->freeBoltMember(_voy._boltGroupId2 + evidId * 2);
+
+ CMapResource *pal = _bVoy->boltEntry(_voy._boltGroupId2 + evidId * 2 + 1)._cMapResource;
+ pal->startFade();
+
+ while (!shouldQuit() && (_eventsManager._fadeStatus & 1))
+ _eventsManager.delay(1);
+ _bVoy->freeBoltMember(_voy._boltGroupId2 + evidId * 2 + 1);
+
+ Common::Array<RectEntry> &hotspots = _bVoy->boltEntry(_playStampGroupId + 4)._rectResource->_entries;
+ int count = hotspots[evidId]._count;
+
+ if (count > 0) {
+ for (int idx = 1; idx <= count; ++idx) {
+ _voy._evPicPtrs[idx - 1] = _bVoy->boltEntry(_voy._boltGroupId2 +
+ (evidId + idx) * 2)._picResource;
+ _voy._evCmPtrs[idx - 1] = _bVoy->boltEntry(_voy._boltGroupId2 +
+ (evidId + idx) * 2 + 1)._cMapResource;
+ }
+ }
+
+ flipPageAndWait();
+ _eventsManager.stopEvidDim();
+
+ if (eventId == 999)
+ _voy.addEvidEventStart(evidId);
+
+ _eventsManager.getMouseInfo();
+
+ int arrIndex = 0;
+ int evidIdx = evidId;
+
+ while (!shouldQuit() && !_eventsManager._rightClick) {
+ _voyeurArea = AREA_EVIDENCE;
+
+ if (_currentVocId != -1 && !_soundManager.getVOCStatus()) {
+ if (_voy._vocSecondsOffset > 60)
+ _voy._vocSecondsOffset = 0;
+
+ _soundManager.startVOCPlay(_currentVocId);
+ }
+
+ _eventsManager.delayClick(600);
+ if (_eventsManager._rightClick)
+ break;
+ if (count == 0 || evidIdx >= eventId)
+ continue;
+
+ PictureResource *pic = _voy._evPicPtrs[arrIndex];
+ _graphicsManager.sDrawPic(pic, *_graphicsManager._vPort,
+ Common::Point((384 - pic->_bounds.width()) / 2,
+ (240 - pic->_bounds.height()) / 2));
+ _voy._evCmPtrs[arrIndex]->startFade();
+ while (!shouldQuit() && (_eventsManager._fadeStatus & 1))
+ _eventsManager.delay(1);
+
+ flipPageAndWait();
+ _eventsManager.delay(6);
+
+ ++evidIdx;
+ ++arrIndex;
+ --count;
+ }
+
+ if (eventId == 999)
+ _voy.addEvidEventEnd(evidIdx);
+
+ for (int idx = 1; idx <= hotspots[evidId]._count; ++idx) {
+ _bVoy->freeBoltMember(_voy._boltGroupId2 + (evidId + idx) * 2);
+ _bVoy->freeBoltMember(_voy._boltGroupId2 + (evidId + idx) * 2 + 1);
+ }
+}
+
+} // End of namespace Voyeur