aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/mohawk/bitmap.cpp706
-rw-r--r--engines/mohawk/bitmap.h159
-rw-r--r--engines/mohawk/console.cpp641
-rw-r--r--engines/mohawk/console.h111
-rw-r--r--engines/mohawk/detection.cpp1042
-rw-r--r--engines/mohawk/dialogs.cpp148
-rw-r--r--engines/mohawk/dialogs.h100
-rw-r--r--engines/mohawk/file.cpp317
-rw-r--r--engines/mohawk/file.h257
-rw-r--r--engines/mohawk/graphics.cpp739
-rw-r--r--engines/mohawk/graphics.h202
-rw-r--r--engines/mohawk/livingbooks.cpp324
-rw-r--r--engines/mohawk/livingbooks.h94
-rw-r--r--engines/mohawk/module.mk39
-rw-r--r--engines/mohawk/mohawk.cpp100
-rw-r--r--engines/mohawk/mohawk.h113
-rw-r--r--engines/mohawk/myst.cpp1540
-rw-r--r--engines/mohawk/myst.h413
-rw-r--r--engines/mohawk/myst_jpeg.cpp66
-rw-r--r--engines/mohawk/myst_jpeg.h54
-rw-r--r--engines/mohawk/myst_pict.cpp247
-rw-r--r--engines/mohawk/myst_pict.h57
-rw-r--r--engines/mohawk/myst_saveload.cpp676
-rw-r--r--engines/mohawk/myst_saveload.h212
-rw-r--r--engines/mohawk/myst_scripts.cpp4864
-rw-r--r--engines/mohawk/myst_scripts.h226
-rw-r--r--engines/mohawk/myst_vars.cpp578
-rw-r--r--engines/mohawk/myst_vars.h60
-rw-r--r--engines/mohawk/riven.cpp586
-rw-r--r--engines/mohawk/riven.h180
-rw-r--r--engines/mohawk/riven_cursors.h404
-rw-r--r--engines/mohawk/riven_external.cpp1467
-rw-r--r--engines/mohawk/riven_external.h252
-rw-r--r--engines/mohawk/riven_saveload.cpp390
-rw-r--r--engines/mohawk/riven_saveload.h66
-rw-r--r--engines/mohawk/riven_scripts.cpp618
-rw-r--r--engines/mohawk/riven_scripts.h127
-rw-r--r--engines/mohawk/riven_vars.cpp324
-rw-r--r--engines/mohawk/sound.cpp530
-rw-r--r--engines/mohawk/sound.h157
-rw-r--r--engines/mohawk/video/cinepak.cpp283
-rw-r--r--engines/mohawk/video/cinepak.h80
-rw-r--r--engines/mohawk/video/qdm2.cpp3064
-rw-r--r--engines/mohawk/video/qdm2.h288
-rw-r--r--engines/mohawk/video/qdm2data.h531
-rw-r--r--engines/mohawk/video/qt_player.cpp1097
-rw-r--r--engines/mohawk/video/qt_player.h246
-rw-r--r--engines/mohawk/video/qtrle.cpp420
-rw-r--r--engines/mohawk/video/qtrle.h57
-rw-r--r--engines/mohawk/video/rpza.cpp208
-rw-r--r--engines/mohawk/video/rpza.h48
-rw-r--r--engines/mohawk/video/smc.cpp385
-rw-r--r--engines/mohawk/video/smc.h58
-rw-r--r--engines/mohawk/video/video.cpp582
-rw-r--r--engines/mohawk/video/video.h188
55 files changed, 26721 insertions, 0 deletions
diff --git a/engines/mohawk/bitmap.cpp b/engines/mohawk/bitmap.cpp
new file mode 100644
index 0000000000..6d4ab84930
--- /dev/null
+++ b/engines/mohawk/bitmap.cpp
@@ -0,0 +1,706 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "mohawk/bitmap.h"
+
+#include "common/debug.h"
+#include "common/util.h"
+#include "common/endian.h"
+#include "common/system.h"
+
+namespace Mohawk {
+
+#define PACK_COMPRESSION (_header.format & kPackMASK)
+#define DRAW_COMPRESSION (_header.format & kDrawMASK)
+
+MohawkBitmap::MohawkBitmap() {
+}
+
+MohawkBitmap::~MohawkBitmap() {
+}
+
+ImageData *MohawkBitmap::decodeImage(Common::SeekableReadStream *stream) {
+ _data = stream;
+ _header.colorTable.palette = NULL;
+
+ // NOTE: Only the bottom 12 bits of width/height/bytesPerRow are
+ // considered valid and bytesPerRow has to be an even number.
+ _header.width = _data->readUint16BE() & 0x3FFF;
+ _header.height = _data->readUint16BE() & 0x3FFF;
+ _header.bytesPerRow = _data->readSint16BE() & 0x3FFE;
+ _header.format = _data->readUint16BE();
+
+ debug (2, "Decoding Mohawk Bitmap (%dx%d, %dbpp, %s Packing + %s Drawing)", _header.width, _header.height, getBitsPerPixel(), getPackName(), getDrawName());
+
+ if (getBitsPerPixel() != 8)
+ error ("Unhandled bpp %d", getBitsPerPixel());
+
+ // Read in the palette if it's here.
+ if (_header.format & kBitmapHasCLUT || (PACK_COMPRESSION == kPackRiven && getBitsPerPixel() == 8)) {
+ _header.colorTable.tableSize = _data->readUint16BE();
+ _header.colorTable.rgbBits = _data->readByte();
+ _header.colorTable.colorCount = _data->readByte();
+ _header.colorTable.palette = (byte *)malloc(256 * 4);
+
+ for (uint16 i = 0; i < 256; i++) {
+ _header.colorTable.palette[i * 4 + 2] = _data->readByte();
+ _header.colorTable.palette[i * 4 + 1] = _data->readByte();
+ _header.colorTable.palette[i * 4] = _data->readByte();
+ _header.colorTable.palette[i * 4 + 3] = 0;
+ }
+ }
+
+ _surface = new Graphics::Surface();
+ _surface->create(_header.width, _header.height, getBitsPerPixel() >> 3);
+
+ unpackImage();
+ drawImage();
+ delete _data;
+
+ return new ImageData(_surface, _header.colorTable.palette);
+}
+
+byte MohawkBitmap::getBitsPerPixel() {
+ switch (_header.format & kBitsPerPixelMask) {
+ case kBitsPerPixel1:
+ return 1;
+ case kBitsPerPixel4:
+ return 4;
+ case kBitsPerPixel8:
+ return 8;
+ case kBitsPerPixel16:
+ return 16;
+ case kBitsPerPixel24:
+ return 24;
+ default:
+ error ("Unknown bits per pixel");
+ }
+
+ return 0;
+}
+
+struct CompressionInfo {
+ uint16 flag;
+ const char *name;
+ void (MohawkBitmap::*func)();
+};
+
+static const CompressionInfo packTable[] = {
+ { kPackNone, "Raw", &MohawkBitmap::unpackRaw },
+ { kPackLZ, "LZ", &MohawkBitmap::unpackLZ },
+ { kPackLZ1, "LZ1", &MohawkBitmap::unpackLZ1 },
+ { kPackRiven, "Riven", &MohawkBitmap::unpackRiven }
+};
+
+const char *MohawkBitmap::getPackName() {
+ for (uint32 i = 0; i < ARRAYSIZE(packTable); i++)
+ if (PACK_COMPRESSION == packTable[i].flag)
+ return packTable[i].name;
+
+ return "Unknown";
+}
+
+void MohawkBitmap::unpackImage() {
+ for (uint32 i = 0; i < ARRAYSIZE(packTable); i++)
+ if (PACK_COMPRESSION == packTable[i].flag) {
+ (this->*packTable[i].func)();
+ return;
+ }
+
+ warning("Unknown Pack Compression");
+}
+
+static const CompressionInfo drawTable[] = {
+ { kDrawRaw, "Raw", &MohawkBitmap::drawRaw },
+ { kDrawRLE8, "RLE8", &MohawkBitmap::drawRLE8 },
+ { kDrawRLE, "RLE", &MohawkBitmap::drawRLE }
+};
+
+const char *MohawkBitmap::getDrawName() {
+ for (uint32 i = 0; i < ARRAYSIZE(drawTable); i++)
+ if (DRAW_COMPRESSION == drawTable[i].flag)
+ return drawTable[i].name;
+
+ return "Unknown";
+}
+
+void MohawkBitmap::drawImage() {
+ for (uint32 i = 0; i < ARRAYSIZE(drawTable); i++)
+ if (DRAW_COMPRESSION == drawTable[i].flag) {
+ (this->*drawTable[i].func)();
+ return;
+ }
+
+ warning("Unknown Draw Compression");
+}
+
+//////////////////////////////////////////
+// Raw "Unpacker"
+//////////////////////////////////////////
+
+void MohawkBitmap::unpackRaw() {
+ // Do nothing :D
+}
+
+//////////////////////////////////////////
+// LZ Unpacker
+//////////////////////////////////////////
+
+#define LEN_BITS 6
+#define MIN_STRING 3 // lower limit for string length
+#define POS_BITS (16 - LEN_BITS)
+#define MAX_STRING ((1 << LEN_BITS) + MIN_STRING - 1) // upper limit for string length
+#define CBUFFERSIZE (1 << POS_BITS) // size of the circular buffer
+#define POS_MASK (CBUFFERSIZE - 1)
+
+Common::SeekableReadStream *MohawkBitmap::decompressLZ(Common::SeekableReadStream *stream, uint32 uncompressedSize) {
+ uint16 flags = 0;
+ uint32 bytesOut = 0;
+ uint16 insertPos = 0;
+
+ // Expand the output buffer to at least the ring buffer size
+ uint32 outBufSize = MAX<int>(uncompressedSize, CBUFFERSIZE);
+
+ byte *outputData = (byte *)malloc(outBufSize);
+ byte *dst = outputData;
+ byte *buf = dst;
+
+ // Clear the buffer to all 0's
+ memset(outputData, 0, outBufSize);
+
+ while (stream->pos() < stream->size()) {
+ flags >>= 1;
+
+ if (!(flags & 0x100))
+ flags = stream->readByte() | 0xff00;
+
+ if (flags & 1) {
+ if (++bytesOut > uncompressedSize)
+ break;
+ *dst++ = stream->readByte();
+ if (++insertPos > POS_MASK) {
+ insertPos = 0;
+ buf += CBUFFERSIZE;
+ }
+ } else {
+ uint16 offLen = stream->readUint16BE();
+ uint16 stringLen = (offLen >> POS_BITS) + MIN_STRING;
+ uint16 stringPos = (offLen + MAX_STRING) & POS_MASK;
+
+ bytesOut += stringLen;
+ if (bytesOut > uncompressedSize)
+ stringLen -= bytesOut - uncompressedSize;
+
+ byte *strPtr = buf + stringPos;
+ if (stringPos > insertPos) {
+ if (bytesOut >= CBUFFERSIZE)
+ strPtr -= CBUFFERSIZE;
+ else if (stringPos + stringLen > POS_MASK) {
+ for (uint16 k = 0; k < stringLen; k++) {
+ *dst++ = *strPtr++;
+ if (++stringPos > POS_MASK) {
+ stringPos = 0;
+ strPtr = outputData;
+ }
+ }
+ insertPos = (insertPos + stringLen) & POS_MASK;
+ if (bytesOut >= uncompressedSize)
+ break;
+ continue;
+ }
+ }
+
+ insertPos += stringLen;
+
+ if (insertPos > POS_MASK) {
+ insertPos &= POS_MASK;
+ buf += CBUFFERSIZE;
+ }
+
+ for (uint16 k = 0; k < stringLen; k++)
+ *dst++ = *strPtr++;
+
+ if (bytesOut >= uncompressedSize)
+ break;
+ }
+ }
+
+ return new Common::MemoryReadStream(outputData, uncompressedSize, Common::DisposeAfterUse::YES);
+}
+
+void MohawkBitmap::unpackLZ() {
+ uint32 uncompressedSize = _data->readUint32BE();
+ /* uint32 compressedSize = */ _data->readUint32BE();
+ uint16 dictSize = _data->readUint16BE();
+
+ // We only support the buffer size of 0x400
+ if (dictSize != CBUFFERSIZE)
+ error("Unsupported dictionary size of %04x", dictSize);
+
+ // Now go and decompress the data
+ Common::SeekableReadStream *decompressedData = decompressLZ(_data, uncompressedSize);
+ delete _data;
+ _data = decompressedData;
+}
+
+//////////////////////////////////////////
+// LZ Unpacker
+//////////////////////////////////////////
+
+void MohawkBitmap::unpackLZ1() {
+ error("STUB: unpackLZ1()");
+}
+
+//////////////////////////////////////////
+// Riven Unpacker
+//////////////////////////////////////////
+
+void MohawkBitmap::unpackRiven() {
+ _data->readUint32BE(); // Unknown, the number is close to bytesPerRow * height. Could be bufSize.
+
+ byte *uncompressedData = (byte *)malloc(_header.bytesPerRow * _header.height);
+ byte *dst = uncompressedData;
+
+ while (!_data->eos() && dst < (uncompressedData + _header.bytesPerRow * _header.height)) {
+ byte cmd = _data->readByte();
+ debug (8, "Riven Pack Command %02x", cmd);
+
+ if (cmd == 0x00) { // End of stream
+ break;
+ } else if (cmd >= 0x01 && cmd <= 0x3f) { // Simple Pixel Duplet Output
+ for (byte i = 0; i < cmd; i++) {
+ *dst++ = _data->readByte();
+ *dst++ = _data->readByte();
+ }
+ } else if (cmd >= 0x40 && cmd <= 0x7f) { // Simple Repetition of last 2 pixels (cmd - 0x40) times
+ byte pixel[] = { *(dst - 2), *(dst - 1) };
+
+ for (byte i = 0; i < (cmd - 0x40); i++) {
+ *dst++ = pixel[0];
+ *dst++ = pixel[1];
+ }
+ } else if (cmd >= 0x80 && cmd <= 0xbf) { // Simple Repetition of last 4 pixels (cmd - 0x80) times
+ byte pixel[] = { *(dst - 4), *(dst - 3), *(dst - 2), *(dst - 1) };
+
+ for (byte i = 0; i < (cmd - 0x80); i++) {
+ *dst++ = pixel[0];
+ *dst++ = pixel[1];
+ *dst++ = pixel[2];
+ *dst++ = pixel[3];
+ }
+ } else { // Subcommand Stream of (cmd - 0xc0) subcommands
+ handleRivenSubcommandStream(cmd - 0xc0, dst);
+ }
+ }
+
+ delete _data;
+ _data = new Common::MemoryReadStream(uncompressedData, _header.bytesPerRow * _header.height, Common::DisposeAfterUse::YES);
+}
+
+static byte getLastTwoBits(byte c) {
+ return (c & 0x03);
+}
+
+static byte getLastThreeBits(byte c) {
+ return (c & 0x07);
+}
+
+static byte getLastFourBits(byte c) {
+ return (c & 0x0f);
+}
+
+#define B_BYTE() \
+ *dst = _data->readByte(); \
+ dst++
+
+#define B_LASTDUPLET() \
+ *dst = *(dst - 2); \
+ dst++
+
+#define B_LASTDUPLET_PLUS_M() \
+ *dst = *(dst - 2) + m; \
+ dst++
+
+#define B_LASTDUPLET_MINUS_M() \
+ *dst = *(dst - 2) - m; \
+ dst++
+
+#define B_LASTDUPLET_PLUS(m) \
+ *dst = *(dst - 2) + (m); \
+ dst++
+
+#define B_LASTDUPLET_MINUS(m) \
+ *dst = *(dst - 2) - (m); \
+ dst++
+
+#define B_PIXEL_MINUS(m) \
+ *dst = *(dst - (m)); \
+ dst++
+
+#define B_NDUPLETS(n) \
+ uint16 m1 = ((getLastTwoBits(cmd) << 8) + _data->readByte()); \
+ for (uint16 j = 0; j < (n); j++) { \
+ *dst = *(dst - m1); \
+ dst++; \
+ } \
+ void dummyFuncToAllowTrailingSemicolon()
+
+
+
+void MohawkBitmap::handleRivenSubcommandStream(byte count, byte *&dst) {
+ for (byte i = 0; i < count; i++) {
+ byte cmd = _data->readByte();
+ uint16 m = getLastFourBits(cmd);
+ debug (9, "Riven Pack Subcommand %02x", cmd);
+
+ // Notes: p = value of the next byte, m = last four bits of the command
+
+ // Arithmetic operations
+ if (cmd >= 0x01 && cmd <= 0x0f) {
+ // Repeat duplet at relative position of -m duplets
+ B_PIXEL_MINUS(m * 2);
+ B_PIXEL_MINUS(m * 2);
+ } else if (cmd == 0x10) {
+ // Repeat last duplet, but set the value of the second pixel to p
+ B_LASTDUPLET();
+ B_BYTE();
+ } else if (cmd >= 0x11 && cmd <= 0x1f) {
+ // Repeat last duplet, but set the value of the second pixel to the value of the -m pixel
+ B_LASTDUPLET();
+ B_PIXEL_MINUS(m);
+ } else if (cmd >= 0x20 && cmd <= 0x2f) {
+ // Repeat last duplet, but add x to second pixel
+ B_LASTDUPLET();
+ B_LASTDUPLET_PLUS_M();
+ } else if (cmd >= 0x30 && cmd <= 0x3f) {
+ // Repeat last duplet, but subtract x from second pixel
+ B_LASTDUPLET();
+ B_LASTDUPLET_MINUS_M();
+ } else if (cmd == 0x40) {
+ // Repeat last duplet, but set the value of the first pixel to p
+ B_BYTE();
+ B_LASTDUPLET();
+ } else if (cmd >= 0x41 && cmd <= 0x4f) {
+ // Output pixel at relative position -m, then second pixel of last duplet
+ B_PIXEL_MINUS(m);
+ B_LASTDUPLET();
+ } else if (cmd == 0x50) {
+ // Output two absolute pixel values, p1 and p2
+ B_BYTE();
+ B_BYTE();
+ } else if (cmd >= 0x51 && cmd <= 0x57) {
+ // Output pixel at relative position -m, then absolute pixel value p
+ // m is the last 3 bits of cmd here, not last 4
+ B_PIXEL_MINUS(getLastThreeBits(cmd));
+ B_BYTE();
+ } else if (cmd >= 0x59 && cmd <= 0x5f) {
+ // Output absolute pixel value p, then pixel at relative position -m
+ // m is the last 3 bits of cmd here, not last 4
+ B_BYTE();
+ B_PIXEL_MINUS(getLastThreeBits(cmd));
+ } else if (cmd >= 0x60 && cmd <= 0x6f) {
+ // Output absolute pixel value p, then (second pixel of last duplet) + x
+ B_BYTE();
+ B_LASTDUPLET_PLUS_M();
+ } else if (cmd >= 0x70 && cmd <= 0x7f) {
+ // Output absolute pixel value p, then (second pixel of last duplet) - x
+ B_BYTE();
+ B_LASTDUPLET_MINUS_M();
+ } else if (cmd >= 0x80 && cmd <= 0x8f) {
+ // Repeat last duplet adding x to the first pixel
+ B_LASTDUPLET_PLUS_M();
+ B_LASTDUPLET();
+ } else if (cmd >= 0x90 && cmd <= 0x9f) {
+ // Output (first pixel of last duplet) + x, then absolute pixel value p
+ B_LASTDUPLET_PLUS_M();
+ B_BYTE();
+ } else if (cmd == 0xa0) {
+ // Repeat last duplet, adding first 4 bits of the next byte
+ // to first pixel and last 4 bits to second
+ byte pattern = _data->readByte();
+ B_LASTDUPLET_PLUS(pattern >> 4);
+ B_LASTDUPLET_PLUS(getLastFourBits(pattern));
+ } else if (cmd == 0xb0) {
+ // Repeat last duplet, adding first 4 bits of the next byte
+ // to first pixel and subtracting last 4 bits from second
+ byte pattern = _data->readByte();
+ B_LASTDUPLET_PLUS(pattern >> 4);
+ B_LASTDUPLET_MINUS(getLastFourBits(pattern));
+ } else if (cmd >= 0xc0 && cmd <= 0xcf) {
+ // Repeat last duplet subtracting x from first pixel
+ B_LASTDUPLET_MINUS_M();
+ B_LASTDUPLET();
+ } else if (cmd >= 0xd0 && cmd <= 0xdf) {
+ // Output (first pixel of last duplet) - x, then absolute pixel value p
+ B_LASTDUPLET_MINUS_M();
+ B_BYTE();
+ } else if (cmd == 0xe0) {
+ // Repeat last duplet, subtracting first 4 bits of the next byte
+ // to first pixel and adding last 4 bits to second
+ byte pattern = _data->readByte();
+ B_LASTDUPLET_MINUS(pattern >> 4);
+ B_LASTDUPLET_PLUS(getLastFourBits(pattern));
+ } else if (cmd == 0xf0 || cmd == 0xff) {
+ // Repeat last duplet, subtracting first 4 bits from the next byte
+ // to first pixel and last 4 bits from second
+ byte pattern = _data->readByte();
+ B_LASTDUPLET_MINUS(pattern >> 4);
+ B_LASTDUPLET_MINUS(getLastFourBits(pattern));
+
+ // Repeat operations
+ // Repeat n duplets from relative position -m (given in pixels, not duplets).
+ // If r is 0, another byte follows and the last pixel is set to that value
+ } else if (cmd >= 0xa4 && cmd <= 0xa7) {
+ B_NDUPLETS(3);
+ B_BYTE();
+ } else if (cmd >= 0xa8 && cmd <= 0xab) {
+ B_NDUPLETS(4);
+ } else if (cmd >= 0xac && cmd <= 0xaf) {
+ B_NDUPLETS(5);
+ B_BYTE();
+ } else if (cmd >= 0xb4 && cmd <= 0xb7) {
+ B_NDUPLETS(6);
+ } else if (cmd >= 0xb8 && cmd <= 0xbb) {
+ B_NDUPLETS(7);
+ B_BYTE();
+ } else if (cmd >= 0xbc && cmd <= 0xbf) {
+ B_NDUPLETS(8);
+ } else if (cmd >= 0xe4 && cmd <= 0xe7) {
+ B_NDUPLETS(9);
+ B_BYTE();
+ } else if (cmd >= 0xe8 && cmd <= 0xeb) {
+ B_NDUPLETS(10); // 5 duplets
+ } else if (cmd >= 0xec && cmd <= 0xef) {
+ B_NDUPLETS(11);
+ B_BYTE();
+ } else if (cmd >= 0xf4 && cmd <= 0xf7) {
+ B_NDUPLETS(12);
+ } else if (cmd >= 0xf8 && cmd <= 0xfb) {
+ B_NDUPLETS(13);
+ B_BYTE();
+ } else if (cmd == 0xfc) {
+ byte b1 = _data->readByte();
+ byte b2 = _data->readByte();
+ uint16 m1 = ((getLastTwoBits(b1) << 8) + b2);
+
+ for (uint16 j = 0; j < ((b1 >> 3) + 1); j++) { // one less iteration
+ B_PIXEL_MINUS(m1);
+ B_PIXEL_MINUS(m1);
+ }
+
+ // last iteration
+ B_PIXEL_MINUS(m1);
+
+ if ((b1 & (1 << 2)) == 0) {
+ B_BYTE();
+ } else {
+ B_PIXEL_MINUS(m1);
+ }
+ } else
+ warning("Unknown Riven Pack Subcommand 0x%02x", cmd);
+ }
+}
+
+//////////////////////////////////////////
+// Raw Drawer
+//////////////////////////////////////////
+
+void MohawkBitmap::drawRaw() {
+ for (uint16 y = 0; y < _header.height; y++) {
+ _data->read((byte *)_surface->pixels + y * _header.width, _header.width);
+ _data->skip(_header.bytesPerRow - _header.width);
+ }
+}
+
+//////////////////////////////////////////
+// RLE8 Drawer
+//////////////////////////////////////////
+
+void MohawkBitmap::drawRLE8() {
+ // A very simple RLE8 scheme is used as a secondary compression on
+ // most images in non-Riven tBMP's.
+
+ for (uint16 i = 0; i < _header.height; i++) {
+ uint16 rowByteCount = _data->readUint16BE();
+ int32 startPos = _data->pos();
+ byte *dst = (byte *)_surface->pixels + i * _header.width;
+ int16 remaining = _header.width;
+
+ // HACK: It seems only the bottom 9 bits are valid for images
+ // TODO: Verify if this is still needed after the buffer clearing fix.
+ rowByteCount &= 0x1ff;
+
+ while (remaining > 0) {
+ byte code = _data->readByte();
+ uint16 runLen = (code & 0x7F) + 1;
+
+ if (runLen > remaining)
+ runLen = remaining;
+
+ if (code & 0x80) {
+ byte val = _data->readByte();
+ for (uint16 j = 0; j < runLen; j++)
+ *dst++ = val;
+ } else {
+ for (uint16 j = 0; j < runLen; j++)
+ *dst++ = _data->readByte();
+ }
+
+ remaining -= runLen;
+ }
+
+ _data->seek(startPos + rowByteCount);
+ }
+}
+
+//////////////////////////////////////////
+// RLE Drawer
+//////////////////////////////////////////
+
+void MohawkBitmap::drawRLE() {
+ warning("STUB: drawRLE()");
+}
+
+//////////////////////////////////////////
+// Myst Bitmap Decoder
+//////////////////////////////////////////
+
+ImageData* MystBitmap::decodeImage(Common::SeekableReadStream* stream) {
+ uint32 uncompressedSize = stream->readUint32LE();
+ Common::SeekableReadStream* bmpStream = decompressLZ(stream, uncompressedSize);
+ delete stream;
+
+ _header.type = bmpStream->readUint16BE();
+
+ if (_header.type != 'BM')
+ error("BMP header not detected");
+
+ _header.size = bmpStream->readUint32LE();
+ assert (_header.size > 0);
+ _header.res1 = bmpStream->readUint16LE();
+ _header.res2 = bmpStream->readUint16LE();
+ _header.imageOffset = bmpStream->readUint32LE();
+
+ _info.size = bmpStream->readUint32LE();
+
+ if (_info.size != 40)
+ error("Only Windows v3 BMP's are supported");
+
+ _info.width = bmpStream->readUint32LE();
+ _info.height = bmpStream->readUint32LE();
+ _info.planes = bmpStream->readUint16LE();
+ _info.bitsPerPixel = bmpStream->readUint16LE();
+ _info.compression = bmpStream->readUint32LE();
+ _info.imageSize = bmpStream->readUint32LE();
+ _info.pixelsPerMeterX = bmpStream->readUint32LE();
+ _info.pixelsPerMeterY = bmpStream->readUint32LE();
+ _info.colorsUsed = bmpStream->readUint32LE();
+ _info.colorsImportant = bmpStream->readUint32LE();
+
+ if (_info.compression != 0)
+ error("Unhandled BMP compression %d", _info.compression);
+
+ if (_info.colorsUsed == 0)
+ _info.colorsUsed = 256;
+
+ // TODO: Myst ME's Help.dat contains WDIB's with 24bpp color.
+ if (_info.bitsPerPixel != 8 && _info.bitsPerPixel != 24)
+ error("%dbpp Bitmaps not supported", _info.bitsPerPixel);
+
+ byte *palData = NULL;
+
+ if (_info.bitsPerPixel == 8) {
+ palData = (byte *)malloc(256 * 4);
+ for (uint16 i = 0; i < _info.colorsUsed; i++) {
+ palData[i * 4 + 2] = bmpStream->readByte();
+ palData[i * 4 + 1] = bmpStream->readByte();
+ palData[i * 4] = bmpStream->readByte();
+ palData[i * 4 + 3] = bmpStream->readByte();
+ }
+ }
+
+ bmpStream->seek(_header.imageOffset);
+
+ Graphics::Surface *surface = new Graphics::Surface();
+ int srcPitch = _info.width * (_info.bitsPerPixel >> 3);
+ const int extraDataLength = (srcPitch % 4) ? 4 - (srcPitch % 4) : 0;
+
+ if (_info.bitsPerPixel == 8) {
+ surface->create(_info.width, _info.height, 1);
+ byte *dst = (byte *)surface->pixels;
+
+ for (uint32 i = 0; i < _info.height; i++) {
+ bmpStream->read(dst + (_info.height - i - 1) * _info.width, _info.width);
+ bmpStream->skip(extraDataLength);
+ }
+ } else {
+ Graphics::PixelFormat pixelFormat = g_system->getScreenFormat();
+ surface->create(_info.width, _info.height, pixelFormat.bytesPerPixel);
+
+ byte *dst = (byte *)surface->pixels + (surface->h - 1) * surface->pitch;
+
+ for (uint32 i = 0; i < _info.height; i++) {
+ for (uint32 j = 0; j < _info.width; j++) {
+ byte b = bmpStream->readByte();
+ byte g = bmpStream->readByte();
+ byte r = bmpStream->readByte();
+
+ if (pixelFormat.bytesPerPixel == 2)
+ *((uint16 *)dst) = pixelFormat.RGBToColor(r, g, b);
+ else
+ *((uint32 *)dst) = pixelFormat.RGBToColor(r, g, b);
+
+ dst += pixelFormat.bytesPerPixel;
+ }
+
+ bmpStream->skip(extraDataLength);
+ dst -= surface->pitch * 2;
+ }
+ }
+
+ delete bmpStream;
+
+ return new ImageData(surface, palData);
+}
+
+ImageData *OldMohawkBitmap::decodeImage(Common::SeekableReadStream *stream) {
+ Common::SeekableSubReadStreamEndian *endianStream = (Common::SeekableSubReadStreamEndian *)stream;
+
+ // The format part is just a guess at this point. Note that the width and height roles have been reversed!
+
+ _header.height = endianStream->readUint16() & 0x3FF;
+ _header.width = endianStream->readUint16() & 0x3FF;
+ _header.bytesPerRow = endianStream->readUint16() & 0x3FE;
+ _header.format = endianStream->readUint16();
+
+ debug(2, "Decoding Old Mohawk Bitmap (%dx%d, %04x Format)", _header.width, _header.height, _header.format);
+
+ warning("Unhandled old Mohawk Bitmap decoding");
+
+ delete stream;
+ return new ImageData(NULL, NULL);
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/bitmap.h b/engines/mohawk/bitmap.h
new file mode 100644
index 0000000000..18c51bc478
--- /dev/null
+++ b/engines/mohawk/bitmap.h
@@ -0,0 +1,159 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MOHAWK_BITMAP_H
+#define MOHAWK_BITMAP_H
+
+#include "mohawk/graphics.h"
+
+#include "common/scummsys.h"
+#include "common/stream.h"
+#include "graphics/surface.h"
+
+namespace Mohawk {
+
+class ImageData;
+
+enum BitmapFormat {
+ kBitsPerPixel1 = 0x0000,
+ kBitsPerPixel4 = 0x0001,
+ kBitsPerPixel8 = 0x0002,
+ kBitsPerPixel16 = 0x0003,
+ kBitsPerPixel24 = 0x0004,
+ kBitsPerPixelMask = 0x0007,
+ kBitmapHasCLUT = 0x0008,
+ kDrawMASK = 0x00f0,
+ kDrawRaw = 0x0000,
+ kDrawRLE8 = 0x0010,
+ kDrawMSRLE8 = 0x0020,
+ kDrawRLE = 0x0030,
+ kPackMASK = 0x0f00,
+ kPackNone = 0x0000,
+ kPackLZ = 0x0100,
+ kPackLZ1 = 0x0200,
+ kPackRiven = 0x0400,
+ kPackXDec = 0x0f00,
+ kFlagMASK = 0xf000,
+ kFlag16_80X86 = 0x1000, // 16 bit pixel data has been converted to 80X86 format
+ kFlag24_MAC = 0x1000 // 24 bit pixel data has been converted to MAC 32 bit format
+};
+
+struct BitmapHeader {
+ uint16 width;
+ uint16 height;
+ int16 bytesPerRow;
+ uint16 format;
+
+ struct ColorTable {
+ uint16 tableSize;
+ byte rgbBits;
+ byte colorCount;
+ byte* palette; // In 8bpp only
+ } colorTable;
+};
+
+class MohawkBitmap {
+public:
+ MohawkBitmap();
+ virtual ~MohawkBitmap();
+
+ virtual ImageData *decodeImage(Common::SeekableReadStream *stream);
+
+ // Unpack Functions
+ void unpackRaw();
+ void unpackLZ();
+ void unpackLZ1();
+ void unpackRiven();
+
+ // Draw Functions
+ void drawRaw();
+ void drawRLE8();
+ void drawRLE();
+
+protected:
+ BitmapHeader _header;
+ byte getBitsPerPixel();
+
+ // The actual LZ decoder
+ static Common::SeekableReadStream *decompressLZ(Common::SeekableReadStream *stream, uint32 uncompressedSize);
+
+private:
+ Common::SeekableReadStream *_data;
+ Graphics::Surface *_surface;
+
+ const char *getPackName();
+ void unpackImage();
+ const char *getDrawName();
+ void drawImage();
+
+ // Riven Decoding
+ void handleRivenSubcommandStream(byte count, byte *&dst);
+};
+
+// Myst uses a different image format than that of other Mohawk games.
+// It essentially uses a Windows bitmap with the LZ encoding from the
+// Mohawk Bitmap format.
+class MystBitmap : public MohawkBitmap {
+public:
+ MystBitmap() : MohawkBitmap() {}
+ ~MystBitmap() {}
+
+ ImageData *decodeImage(Common::SeekableReadStream *stream);
+
+private:
+ struct BitmapHeader {
+ uint16 type;
+ uint32 size;
+ uint16 res1;
+ uint16 res2;
+ uint32 imageOffset;
+ } _header;
+
+ struct InfoHeader {
+ uint32 size;
+ uint32 width;
+ uint32 height;
+ uint16 planes;
+ uint16 bitsPerPixel;
+ uint32 compression;
+ uint32 imageSize;
+ uint32 pixelsPerMeterX;
+ uint32 pixelsPerMeterY;
+ uint32 colorsUsed;
+ uint32 colorsImportant;
+ } _info;
+};
+
+class OldMohawkBitmap : public MohawkBitmap {
+public:
+ OldMohawkBitmap() : MohawkBitmap() {}
+ ~OldMohawkBitmap() {}
+
+ ImageData *decodeImage(Common::SeekableReadStream *stream);
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/console.cpp b/engines/mohawk/console.cpp
new file mode 100644
index 0000000000..f2020a5144
--- /dev/null
+++ b/engines/mohawk/console.cpp
@@ -0,0 +1,641 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "mohawk/console.h"
+#include "mohawk/myst.h"
+#include "mohawk/myst_scripts.h"
+#include "mohawk/graphics.h"
+#include "mohawk/riven.h"
+#include "mohawk/livingbooks.h"
+
+namespace Mohawk {
+
+MystConsole::MystConsole(MohawkEngine_Myst *vm) : GUI::Debugger(), _vm(vm) {
+ DCmd_Register("changeCard", WRAP_METHOD(MystConsole, Cmd_ChangeCard));
+ DCmd_Register("curCard", WRAP_METHOD(MystConsole, Cmd_CurCard));
+ DCmd_Register("var", WRAP_METHOD(MystConsole, Cmd_Var));
+ DCmd_Register("curStack", WRAP_METHOD(MystConsole, Cmd_CurStack));
+ DCmd_Register("changeStack", WRAP_METHOD(MystConsole, Cmd_ChangeStack));
+ DCmd_Register("drawImage", WRAP_METHOD(MystConsole, Cmd_DrawImage));
+ DCmd_Register("drawRect", WRAP_METHOD(MystConsole, Cmd_DrawRect));
+ DCmd_Register("setResourceEnable", WRAP_METHOD(MystConsole, Cmd_SetResourceEnable));
+ DCmd_Register("playSound", WRAP_METHOD(MystConsole, Cmd_PlaySound));
+ DCmd_Register("stopSound", WRAP_METHOD(MystConsole, Cmd_StopSound));
+ DCmd_Register("playMovie", WRAP_METHOD(MystConsole, Cmd_PlayMovie));
+ DCmd_Register("disableInitOpcodes", WRAP_METHOD(MystConsole, Cmd_DisableInitOpcodes));
+}
+
+MystConsole::~MystConsole() {
+}
+
+void MystConsole::preEnter() {
+ _vm->_sound->pauseSound();
+}
+
+void MystConsole::postEnter() {
+ _vm->_sound->resumeSound();
+}
+
+bool MystConsole::Cmd_ChangeCard(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("Usage: changeCard <card>\n");
+ return true;
+ }
+
+ _vm->_sound->stopSound();
+ _vm->changeToCard((uint16)atoi(argv[1]));
+
+ return false;
+}
+
+bool MystConsole::Cmd_CurCard(int argc, const char **argv) {
+ DebugPrintf("Current Card: %d\n", _vm->getCurCard());
+ return true;
+}
+
+bool MystConsole::Cmd_Var(int argc, const char **argv) {
+ if (argc == 1) {
+ DebugPrintf("Usage: var <var> (<value>)\n");
+ return true;
+ }
+
+ if (argc > 2)
+ _vm->_varStore->setVar((uint16)atoi(argv[1]), (uint32)atoi(argv[2]));
+
+ DebugPrintf("%d = %d\n", (uint16)atoi(argv[1]), _vm->_varStore->getVar((uint16)atoi(argv[1])));
+
+ return true;
+}
+
+static const char *mystStackNames[12] = {
+ "Channelwood",
+ "Credits",
+ "Demo",
+ "D'ni",
+ "Intro",
+ "MakingOf",
+ "Mechanical",
+ "Myst",
+ "Selenitic",
+ "Slideshow",
+ "SneakPreview",
+ "Stoneship"
+};
+
+static const uint16 default_start_card[12] = {
+ 3137,
+ 10000,
+ 2001, // TODO: Should be 2000?
+ 5038,
+ 2, // TODO: Should be 1?
+ 1,
+ 6122,
+ 4134,
+ 1282,
+ 1000,
+ 3000,
+ 2029
+};
+
+bool MystConsole::Cmd_CurStack(int argc, const char **argv) {
+ DebugPrintf("Current Stack: %s\n", mystStackNames[_vm->getCurStack()]);
+ return true;
+}
+
+bool MystConsole::Cmd_ChangeStack(int argc, const char **argv) {
+ if (argc != 2 && argc != 3) {
+ DebugPrintf("Usage: changeStack <stack> [<card>]\n\n");
+ DebugPrintf("Stacks:\n=======\n");
+
+ for (byte i = 0; i < ARRAYSIZE(mystStackNames); i++)
+ DebugPrintf(" %s\n", mystStackNames[i]);
+
+ DebugPrintf("\n");
+
+ return true;
+ }
+
+ byte stackNum = 0;
+
+ for (byte i = 1; i <= ARRAYSIZE(mystStackNames); i++)
+ if (!scumm_stricmp(argv[1], mystStackNames[i - 1])) {
+ stackNum = i;
+ break;
+ }
+
+ if (!stackNum) {
+ DebugPrintf("\'%s\' is not a stack name!\n", argv[1]);
+ return true;
+ }
+
+ // We need to stop any playing sound when we change the stack
+ // as the next card could continue playing it if it.
+ _vm->_sound->stopSound();
+
+ _vm->changeToStack(stackNum - 1);
+
+ if (argc == 3)
+ _vm->changeToCard((uint16)atoi(argv[2]));
+ else
+ _vm->changeToCard(default_start_card[stackNum - 1]);
+
+ return false;
+}
+
+bool MystConsole::Cmd_DrawImage(int argc, const char **argv) {
+ if (argc != 2 && argc != 6) {
+ DebugPrintf("Usage: drawImage <image> [<left> <top> <right> <bottom>]\n");
+ return true;
+ }
+
+ Common::Rect rect;
+
+ if (argc == 2)
+ rect = Common::Rect(0, 0, 544, 333);
+ else
+ rect = Common::Rect((uint16)atoi(argv[2]), (uint16)atoi(argv[3]), (uint16)atoi(argv[4]), (uint16)atoi(argv[5]));
+
+ _vm->_gfx->copyImageToScreen((uint16)atoi(argv[1]), rect);
+ return false;
+}
+
+bool MystConsole::Cmd_DrawRect(int argc, const char **argv) {
+ if (argc < 5) {
+ DebugPrintf("Usage: drawRect <left> <top> <right> <bottom>\n");
+ return true;
+ }
+
+ _vm->_gfx->drawRect(Common::Rect((uint16)atoi(argv[1]), (uint16)atoi(argv[2]), (uint16)atoi(argv[3]), (uint16)atoi(argv[4])), true);
+ return false;
+}
+
+bool MystConsole::Cmd_SetResourceEnable(int argc, const char **argv) {
+ if (argc < 3) {
+ DebugPrintf("Usage: setResourceEnable <resource id> <bool>\n");
+ return true;
+ }
+
+ _vm->setResourceEnabled((uint16)atoi(argv[1]), atoi(argv[2]) == 1);
+ return true;
+}
+
+bool MystConsole::Cmd_PlaySound(int argc, const char **argv) {
+ if (argc == 1) {
+ DebugPrintf("Usage: playSound <value>\n");
+
+ return true;
+ }
+
+ _vm->_sound->stopSound();
+ _vm->_sound->playSound((uint16)atoi(argv[1]));
+
+ return false;
+}
+
+bool MystConsole::Cmd_StopSound(int argc, const char **argv) {
+ DebugPrintf("Stopping Sound\n");
+
+ _vm->_sound->stopSound();
+
+ return true;
+}
+
+bool MystConsole::Cmd_PlayMovie(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("Usage: playMovie <name> [<stack>] [<left> <top>]\n");
+ DebugPrintf("NOTE: The movie will play *once* in the background.\n");
+ return true;
+ }
+
+ int8 stackNum = 0;
+
+ if (argc == 3 || argc > 4) {
+ for (byte i = 1; i <= ARRAYSIZE(mystStackNames); i++)
+ if (!scumm_stricmp(argv[2], mystStackNames[i - 1])) {
+ stackNum = i;
+ break;
+ }
+
+ if (!stackNum) {
+ DebugPrintf("\'%s\' is not a stack name!\n", argv[2]);
+ return true;
+ }
+ }
+
+ if (argc == 2)
+ _vm->_video->playBackgroundMovie(argv[1], 0, 0);
+ else if (argc == 3)
+ _vm->_video->playBackgroundMovie(_vm->wrapMovieFilename(argv[1], stackNum - 1), 0, 0);
+ else if (argc == 4)
+ _vm->_video->playBackgroundMovie(argv[1], atoi(argv[2]), atoi(argv[3]));
+ else
+ _vm->_video->playBackgroundMovie(_vm->wrapMovieFilename(argv[1], stackNum - 1), atoi(argv[3]), atoi(argv[4]));
+
+ return false;
+}
+
+bool MystConsole::Cmd_DisableInitOpcodes(int argc, const char **argv) {
+ if (argc != 1) {
+ DebugPrintf("Usage: disableInitOpcodes\n");
+
+ return true;
+ }
+
+ _vm->_scriptParser->disableInitOpcodes();
+
+ return true;
+}
+
+RivenConsole::RivenConsole(MohawkEngine_Riven *vm) : GUI::Debugger(), _vm(vm) {
+ DCmd_Register("changeCard", WRAP_METHOD(RivenConsole, Cmd_ChangeCard));
+ DCmd_Register("curCard", WRAP_METHOD(RivenConsole, Cmd_CurCard));
+ DCmd_Register("var", WRAP_METHOD(RivenConsole, Cmd_Var));
+ DCmd_Register("playSound", WRAP_METHOD(RivenConsole, Cmd_PlaySound));
+ DCmd_Register("playSLST", WRAP_METHOD(RivenConsole, Cmd_PlaySLST));
+ DCmd_Register("stopSound", WRAP_METHOD(RivenConsole, Cmd_StopSound));
+ DCmd_Register("curStack", WRAP_METHOD(RivenConsole, Cmd_CurStack));
+ DCmd_Register("changeStack", WRAP_METHOD(RivenConsole, Cmd_ChangeStack));
+ DCmd_Register("restart", WRAP_METHOD(RivenConsole, Cmd_Restart));
+ DCmd_Register("hotspots", WRAP_METHOD(RivenConsole, Cmd_Hotspots));
+ DCmd_Register("zipMode", WRAP_METHOD(RivenConsole, Cmd_ZipMode));
+ DCmd_Register("dumpScript", WRAP_METHOD(RivenConsole, Cmd_DumpScript));
+ DCmd_Register("listZipCards", WRAP_METHOD(RivenConsole, Cmd_ListZipCards));
+ DCmd_Register("getRMAP", WRAP_METHOD(RivenConsole, Cmd_GetRMAP));
+}
+
+RivenConsole::~RivenConsole() {
+}
+
+void RivenConsole::preEnter() {
+ _vm->_sound->pauseSound();
+ _vm->_sound->pauseSLST();
+}
+
+void RivenConsole::postEnter() {
+ _vm->_sound->resumeSound();
+ _vm->_sound->resumeSLST();
+}
+
+bool RivenConsole::Cmd_ChangeCard(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("Usage: changeCard <card>\n");
+ return true;
+ }
+
+ _vm->_sound->stopSound();
+ _vm->_sound->stopAllSLST();
+ _vm->changeToCard((uint16)atoi(argv[1]));
+
+ return false;
+}
+
+bool RivenConsole::Cmd_CurCard(int argc, const char **argv) {
+ DebugPrintf("Current Card: %d\n", _vm->getCurCard());
+
+ return true;
+}
+
+bool RivenConsole::Cmd_Var(int argc, const char **argv) {
+ if (argc == 1) {
+ DebugPrintf("Usage: var <var name> (<value>)\n");
+ return true;
+ }
+
+ uint32 *globalVar = _vm->matchVarToString(argv[1]);
+
+ if (!globalVar) {
+ DebugPrintf("Unknown variable \'%s\'\n", argv[1]);
+ return true;
+ }
+
+ if (argc > 2)
+ *globalVar = (uint32)atoi(argv[2]);
+
+ DebugPrintf("%s = %d\n", argv[1], *globalVar);
+
+ return true;
+}
+
+bool RivenConsole::Cmd_PlaySound(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("Usage: playSound <value> (<use main sound file, default = true>)\n");
+ DebugPrintf("The main sound file is default, but you can use the word \'false\' to make it use the current stack file.\n");
+
+ return true;
+ }
+
+ _vm->_sound->stopSound();
+ _vm->_sound->stopAllSLST();
+
+ bool mainSoundFile = (argc < 3) || (scumm_stricmp(argv[2], "false") != 0);
+
+ _vm->_sound->playSound((uint16)atoi(argv[1]), mainSoundFile);
+
+ return false;
+}
+
+bool RivenConsole::Cmd_PlaySLST(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("Usage: playSLST <slst index> <card, default = current>\n");
+
+ return true;
+ }
+
+ _vm->_sound->stopSound();
+ _vm->_sound->stopAllSLST();
+
+ uint16 card = _vm->getCurCard();
+
+ if (argc == 3)
+ card = (uint16)atoi(argv[2]);
+
+ _vm->_sound->playSLST((uint16)atoi(argv[1]), card);
+
+ return false;
+}
+
+bool RivenConsole::Cmd_StopSound(int argc, const char **argv) {
+ DebugPrintf("Stopping Sound\n");
+
+ _vm->_sound->stopSound();
+ _vm->_sound->stopAllSLST();
+
+ return true;
+}
+
+bool RivenConsole::Cmd_CurStack(int argc, const char **argv) {
+ DebugPrintf("Current Stack: %s\n", _vm->getStackName(_vm->getCurStack()).c_str());
+
+ return true;
+}
+
+bool RivenConsole::Cmd_ChangeStack(int argc, const char **argv) {
+ byte i;
+
+ if (argc < 3) {
+ DebugPrintf("Usage: changeStack <stack> <card>\n\n");
+ DebugPrintf("Stacks:\n=======\n");
+
+ for (i = 0; i <= tspit; i++)
+ DebugPrintf(" %s\n", _vm->getStackName(i).c_str());
+
+ DebugPrintf("\n");
+
+ return true;
+ }
+
+ byte stackNum = 0;
+
+ for (i = 1; i <= tspit + 1; i++)
+ if (!scumm_stricmp(argv[1], _vm->getStackName(i - 1).c_str())) {
+ stackNum = i;
+ break;
+ }
+
+ if (!stackNum) {
+ DebugPrintf("\'%s\' is not a stack name!\n", argv[1]);
+ return true;
+ }
+
+ _vm->changeToStack(stackNum - 1);
+ _vm->changeToCard((uint16)atoi(argv[2]));
+
+ return false;
+}
+
+bool RivenConsole::Cmd_Restart(int argc, const char **argv) {
+ _vm->initVars();
+ _vm->changeToStack(aspit);
+ _vm->changeToCard(1);
+
+ return false;
+}
+
+bool RivenConsole::Cmd_Hotspots(int argc, const char **argv) {
+ DebugPrintf("Current card (%d) has %d hotspots:\n", _vm->getCurCard(), _vm->getHotspotCount());
+
+ for (uint16 i = 0; i < _vm->getHotspotCount(); i++) {
+ DebugPrintf("Hotspot %d, index %d, BLST ID %d (", i, _vm->_hotspots[i].index, _vm->_hotspots[i].blstID);
+
+ if (_vm->_hotspots[i].enabled)
+ DebugPrintf("enabled)\n");
+ else
+ DebugPrintf("disabled)\n");
+
+ DebugPrintf(" Name = %s\n", _vm->getHotspotName(i).c_str());
+ }
+
+ return true;
+}
+
+bool RivenConsole::Cmd_ZipMode(int argc, const char **argv) {
+ uint32 *zipModeActive = _vm->matchVarToString("azip");
+ *zipModeActive = !(*zipModeActive);
+
+ DebugPrintf("Zip Mode is ");
+ DebugPrintf((*zipModeActive) ? "Enabled" : "Disabled");
+ DebugPrintf("\n");
+ return true;
+}
+
+bool RivenConsole::Cmd_DumpScript(int argc, const char **argv) {
+ if (argc < 4) {
+ DebugPrintf("Usage: dumpScript <stack> <CARD or HSPT> <card>\n");
+ return true;
+ }
+
+ uint16 oldStack = _vm->getCurStack();
+
+ byte newStack = 0;
+
+ for (byte i = 1; i <= tspit + 1; i++)
+ if (!scumm_stricmp(argv[1], _vm->getStackName(i - 1).c_str())) {
+ newStack = i;
+ break;
+ }
+
+ if (!newStack) {
+ DebugPrintf("\'%s\' is not a stack name!\n", argv[1]);
+ return true;
+ }
+
+ newStack--;
+ _vm->changeToStack(newStack);
+
+ // Load in Variable Names
+ Common::SeekableReadStream *nameStream = _vm->getRawData(ID_NAME, VariableNames);
+ Common::StringList varNames;
+
+ uint16 namesCount = nameStream->readUint16BE();
+ uint16 *stringOffsets = new uint16[namesCount];
+ for (uint16 i = 0; i < namesCount; i++)
+ stringOffsets[i] = nameStream->readUint16BE();
+ nameStream->seek(namesCount * 2, SEEK_CUR);
+ int32 curNamesPos = nameStream->pos();
+
+ for (uint32 i = 0; i < namesCount; i++) {
+ nameStream->seek(curNamesPos + stringOffsets[i]);
+
+ Common::String name = Common::String::emptyString;
+ for (char c = nameStream->readByte(); c; c = nameStream->readByte())
+ name += c;
+ varNames.push_back(name);
+ }
+ delete nameStream;
+
+ // Load in External Command Names
+ nameStream = _vm->getRawData(ID_NAME, ExternalCommandNames);
+ Common::StringList xNames;
+
+ namesCount = nameStream->readUint16BE();
+ stringOffsets = new uint16[namesCount];
+ for (uint16 i = 0; i < namesCount; i++)
+ stringOffsets[i] = nameStream->readUint16BE();
+ nameStream->seek(namesCount * 2, SEEK_CUR);
+ curNamesPos = nameStream->pos();
+
+ for (uint32 i = 0; i < namesCount; i++) {
+ nameStream->seek(curNamesPos + stringOffsets[i]);
+
+ Common::String name = Common::String::emptyString;
+ for (char c = nameStream->readByte(); c; c = nameStream->readByte())
+ name += c;
+ xNames.push_back(name);
+ }
+ delete nameStream;
+
+ // Get CARD/HSPT data and dump their scripts
+ if (!scumm_stricmp(argv[2], "CARD")) {
+ printf ("\n\nDumping scripts for %s\'s card %d!\n", argv[1], (uint16)atoi(argv[3]));
+ printf ("==================================\n\n");
+ Common::SeekableReadStream *cardStream = _vm->getRawData(MKID_BE('CARD'), (uint16)atoi(argv[3]));
+ cardStream->seek(4);
+ RivenScriptList scriptList = RivenScript::readScripts(_vm, cardStream);
+ for (uint32 i = 0; i < scriptList.size(); i++)
+ scriptList[i]->dumpScript(varNames, xNames, 0);
+ delete cardStream;
+ } else if (!scumm_stricmp(argv[2], "HSPT")) {
+ printf ("\n\nDumping scripts for %s\'s card %d hotspots!\n", argv[1], (uint16)atoi(argv[3]));
+ printf ("===========================================\n\n");
+
+ Common::SeekableReadStream *hsptStream = _vm->getRawData(MKID_BE('HSPT'), (uint16)atoi(argv[3]));
+
+ uint16 hotspotCount = hsptStream->readUint16BE();
+
+ for (uint16 i = 0; i < hotspotCount; i++) {
+ printf ("Hotspot %d:\n", i);
+ hsptStream->seek(22, SEEK_CUR); // Skip non-script related stuff
+ RivenScriptList scriptList = RivenScript::readScripts(_vm, hsptStream);
+ for (uint32 j = 0; j < scriptList.size(); j++)
+ scriptList[j]->dumpScript(varNames, xNames, 1);
+ }
+
+ delete hsptStream;
+ } else {
+ DebugPrintf("%s doesn't have any scripts!\n", argv[2]);
+ }
+
+ printf("\n\n");
+
+ _vm->changeToStack(oldStack);
+
+ DebugPrintf("Script dump complete.\n");
+
+ return true;
+}
+
+bool RivenConsole::Cmd_ListZipCards(int argc, const char **argv) {
+ if (_vm->_zipModeData.size() == 0) {
+ DebugPrintf("No zip card data.\n");
+ } else {
+ DebugPrintf("Listing zip cards:\n");
+ for (uint32 i = 0; i < _vm->_zipModeData.size(); i++)
+ DebugPrintf("ID = %d, Name = %s\n", _vm->_zipModeData[i].id, _vm->_zipModeData[i].name.c_str());
+ }
+
+ return true;
+}
+
+bool RivenConsole::Cmd_GetRMAP(int argc, const char **argv) {
+ Common::SeekableReadStream *rmapStream = _vm->getRawData(ID_RMAP, 1);
+ rmapStream->seek(_vm->getCurCard() * 4);
+ DebugPrintf("RMAP for %s %d = %08x\n", _vm->getStackName(_vm->getCurStack()).c_str(), _vm->getCurCard(), rmapStream->readUint32BE());
+ delete rmapStream;
+
+ return true;
+}
+
+LivingBooksConsole::LivingBooksConsole(MohawkEngine_LivingBooks *vm) : GUI::Debugger(), _vm(vm) {
+ DCmd_Register("playSound", WRAP_METHOD(LivingBooksConsole, Cmd_PlaySound));
+ DCmd_Register("stopSound", WRAP_METHOD(LivingBooksConsole, Cmd_StopSound));
+ DCmd_Register("drawImage", WRAP_METHOD(LivingBooksConsole, Cmd_DrawImage));
+}
+
+LivingBooksConsole::~LivingBooksConsole() {
+}
+
+void LivingBooksConsole::preEnter() {
+ _vm->_sound->pauseSound();
+}
+
+void LivingBooksConsole::postEnter() {
+ _vm->_sound->resumeSound();
+}
+
+bool LivingBooksConsole::Cmd_PlaySound(int argc, const char **argv) {
+ if (argc == 1) {
+ DebugPrintf("Usage: playSound <value>\n");
+
+ return true;
+ }
+
+ _vm->_sound->stopSound();
+ _vm->_sound->playSound((uint16)atoi(argv[1]));
+
+ return false;
+}
+
+bool LivingBooksConsole::Cmd_StopSound(int argc, const char **argv) {
+ DebugPrintf("Stopping Sound\n");
+
+ _vm->_sound->stopSound();
+
+ return true;
+}
+
+bool LivingBooksConsole::Cmd_DrawImage(int argc, const char **argv) {
+ if (argc == 1) {
+ DebugPrintf("Usage: drawImage <value>\n");
+ return true;
+ }
+
+ if (_vm->getGameType() == GType_OLDLIVINGBOOKS)
+ DebugPrintf("This isn't supported in the old Living Books games (yet)!\n");
+
+ _vm->_gfx->copyImageToScreen((uint16)atoi(argv[1]));
+ return _vm->getGameType() != GType_OLDLIVINGBOOKS;
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/console.h b/engines/mohawk/console.h
new file mode 100644
index 0000000000..bd699bc8b7
--- /dev/null
+++ b/engines/mohawk/console.h
@@ -0,0 +1,111 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MOHAWK_CONSOLE_H
+#define MOHAWK_CONSOLE_H
+
+#include "gui/debugger.h"
+
+namespace Mohawk {
+
+class MohawkEngine_Myst;
+class MohawkEngine_Riven;
+class MohawkEngine_LivingBooks;
+
+class MystConsole : public GUI::Debugger {
+public:
+ MystConsole(MohawkEngine_Myst *vm);
+ virtual ~MystConsole(void);
+
+protected:
+ virtual void preEnter();
+ virtual void postEnter();
+
+private:
+ MohawkEngine_Myst *_vm;
+
+ bool Cmd_ChangeCard(int argc, const char **argv);
+ bool Cmd_CurCard(int argc, const char **argv);
+ bool Cmd_Var(int argc, const char **argv);
+ bool Cmd_DrawImage(int argc, const char **argv);
+ bool Cmd_DrawRect(int argc, const char **argv);
+ bool Cmd_SetResourceEnable(int argc, const char **argv);
+ bool Cmd_CurStack(int argc, const char **argv);
+ bool Cmd_ChangeStack(int argc, const char **argv);
+ bool Cmd_PlaySound(int argc, const char **argv);
+ bool Cmd_StopSound(int argc, const char **argv);
+ bool Cmd_PlayMovie(int argc, const char **argv);
+ bool Cmd_DisableInitOpcodes(int argc, const char **argv);
+};
+
+class RivenConsole : public GUI::Debugger {
+public:
+ RivenConsole(MohawkEngine_Riven *vm);
+ virtual ~RivenConsole(void);
+
+protected:
+ virtual void preEnter();
+ virtual void postEnter();
+
+private:
+ MohawkEngine_Riven *_vm;
+
+ bool Cmd_ChangeCard(int argc, const char **argv);
+ bool Cmd_CurCard(int argc, const char **argv);
+ bool Cmd_Var(int argc, const char **argv);
+ bool Cmd_PlaySound(int argc, const char **argv);
+ bool Cmd_PlaySLST(int argc, const char **argv);
+ bool Cmd_StopSound(int argc, const char **argv);
+ bool Cmd_CurStack(int argc, const char **argv);
+ bool Cmd_ChangeStack(int argc, const char **argv);
+ bool Cmd_Restart(int argc, const char **argv);
+ bool Cmd_Hotspots(int argc, const char **argv);
+ bool Cmd_ZipMode(int argc, const char **argv);
+ bool Cmd_RunAllBlocks(int argc, const char **argv);
+ bool Cmd_DumpScript(int argc, const char **argv);
+ bool Cmd_ListZipCards(int argc, const char **argv);
+ bool Cmd_GetRMAP(int argc, const char **argv);
+};
+
+class LivingBooksConsole : public GUI::Debugger {
+public:
+ LivingBooksConsole(MohawkEngine_LivingBooks *vm);
+ virtual ~LivingBooksConsole(void);
+
+protected:
+ virtual void preEnter();
+ virtual void postEnter();
+
+private:
+ MohawkEngine_LivingBooks *_vm;
+
+ bool Cmd_PlaySound(int argc, const char **argv);
+ bool Cmd_StopSound(int argc, const char **argv);
+ bool Cmd_DrawImage(int argc, const char **argv);
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/detection.cpp b/engines/mohawk/detection.cpp
new file mode 100644
index 0000000000..7e310c8760
--- /dev/null
+++ b/engines/mohawk/detection.cpp
@@ -0,0 +1,1042 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "base/plugins.h"
+
+#include "engines/advancedDetector.h"
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/savefile.h"
+
+#include "mohawk/myst.h"
+#include "mohawk/riven.h"
+#include "mohawk/livingbooks.h"
+
+// Define this to enable detection of other Broderbund titles which use Mohawk (besides Myst/Riven)
+#define DETECT_BRODERBUND_TITLES
+
+namespace Mohawk {
+
+struct MohawkGameDescription {
+ ADGameDescription desc;
+
+ uint8 gameType;
+ uint32 features;
+ uint16 version;
+};
+
+const char* MohawkEngine::getGameId() const {
+ return _gameDescription->desc.gameid;
+}
+
+uint32 MohawkEngine::getFeatures() const {
+ return _gameDescription->features;
+}
+
+Common::Platform MohawkEngine::getPlatform() const {
+ return _gameDescription->desc.platform;
+}
+
+uint16 MohawkEngine::getVersion() const {
+ return _gameDescription->version;
+}
+
+uint8 MohawkEngine::getGameType() {
+ return _gameDescription->gameType;
+}
+
+Common::String MohawkEngine_LivingBooks::getBookInfoFileName() {
+ return _gameDescription->desc.filesDescriptions[0].fileName;
+}
+
+Common::Language MohawkEngine::getLanguage() {
+ return _gameDescription->desc.language;
+}
+
+bool MohawkEngine::hasFeature(EngineFeature f) const {
+ return
+ (f == kSupportsRTL);
+}
+
+bool MohawkEngine_Myst::hasFeature(EngineFeature f) const {
+ return
+ MohawkEngine::hasFeature(f)
+ || (f == kSupportsLoadingDuringRuntime)
+ || (f == kSupportsSavingDuringRuntime);
+}
+
+bool MohawkEngine_Riven::hasFeature(EngineFeature f) const {
+ return
+ MohawkEngine::hasFeature(f)
+ || (f == kSupportsLoadingDuringRuntime)
+ || (f == kSupportsSavingDuringRuntime);
+}
+
+} // End of Namespace Mohawk
+
+static const PlainGameDescriptor mohawkGames[] = {
+ {"mohawk", "Mohawk Game"},
+ {"myst", "Myst"},
+ {"MakingOfMyst", "The Making of Myst"},
+ {"riven", "Riven: The Sequel to Myst"},
+#ifdef DETECT_BRODERBUND_TITLES
+ {"zoombini", "Logical Journey of the Zoombinis Deluxe"},
+ {"csworld", "Where in the World is Carmen Sandiego?"},
+ {"csamtrak", "Where in America is Carmen Sandiego? (The Great Amtrak Train Adventure)"},
+ {"maggiess", "Maggie's Farmyard Adventure"},
+ {"jamesmath", "James Discovers/Explores Math"},
+ {"treehouse", "The Treehouse"},
+ {"greeneggs", "Green Eggs and Ham"},
+ {"1stdegree", "In the 1st Degree"},
+ {"csusa", "Where in the USA is Carmen Sandiego?"},
+ {"tortoise", "Aesop's Fables: The Tortoise and the Hare"},
+ {"arthur", "Arthur's Teacher Troubles"},
+ {"grandma", "Just Grandma and Me"},
+ {"ruff", "Ruff's Bone"},
+ {"newkid", "The New Kid on the Block"},
+ {"arthurrace", "Arthur's Reading Race"},
+#endif
+ {0, 0}
+};
+
+
+namespace Mohawk {
+
+static const MohawkGameDescription gameDescriptions[] = {
+ // Myst
+ // English Windows 3.11
+ // From clone2727
+ {
+ {
+ "myst",
+ "",
+ AD_ENTRY1("MYST.DAT", "ae3258c9c90128d274aa6a790b3ad181"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_MYST,
+ 0,
+ 0,
+ },
+
+ // Myst Demo
+ // English Windows 3.11
+ // From CD-ROM Today July, 1994
+ {
+ {
+ "myst",
+ "Demo",
+ AD_ENTRY1("DEMO.DAT", "c39303dd53fb5c4e7f3c23231c606cd0"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_DEMO,
+ Common::GUIO_NONE
+ },
+ GType_MYST,
+ GF_DEMO,
+ 0,
+ },
+
+ // Myst
+ // German Windows 3.11
+ // From clone2727
+ {
+ {
+ "myst",
+ "",
+ AD_ENTRY1("MYST.DAT", "4beb3366ed3f3b9bfb6e81a14a43bdcc"),
+ Common::DE_DEU,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_MYST,
+ 0,
+ 0,
+ },
+
+ // Myst
+ // German Windows 3.11
+ // From LordHoto
+ {
+ {
+ "myst",
+ "",
+ AD_ENTRY1("MYST.DAT", "e0937cca1ab125e48e30dc3cd5046ddf"),
+ Common::DE_DEU,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_MYST,
+ 0,
+ 0,
+ },
+
+ // Myst
+ // Spanish Windows ?
+ // From jvprat
+ {
+ {
+ "myst",
+ "",
+ AD_ENTRY1("MYST.DAT", "f7e7d7ca69934f1351b5acd4fe4d44c2"),
+ Common::ES_ESP,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_MYST,
+ 0,
+ 0,
+ },
+
+ // Myst
+ // Japanese Windows 3.11
+ // From clone2727
+ {
+ {
+ "myst",
+ "",
+ AD_ENTRY1("MYST.DAT", "032c88e3b7e8db4ca475e7b7db9a66bb"),
+ Common::JA_JPN,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_MYST,
+ 0,
+ 0,
+ },
+
+ // Making of Myst
+ // English Windows 3.11
+ // From clone2727
+ {
+ {
+ "MakingOfMyst",
+ "",
+ AD_ENTRY1("MAKING.DAT", "f6387e8f0f7b8a3e42c95294315d6a0e"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_MAKINGOF,
+ 0,
+ 0,
+ },
+
+ // Making of Myst
+ // Japanese Windows 3.11
+ // From clone2727
+ {
+ {
+ "MakingOfMyst",
+ "",
+ AD_ENTRY1("MAKING.DAT", "03ff62607e64419ab2b6ebf7b7bcdf63"),
+ Common::JA_JPN,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_MAKINGOF,
+ 0,
+ 0,
+ },
+
+ // Myst Masterpiece Edition
+ // English Windows
+ // From clone2727
+ {
+ {
+ "myst",
+ "Masterpiece Edition",
+ AD_ENTRY1("MYST.DAT", "c4cae9f143b5947262e6cb2397e1617e"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_MYST,
+ GF_ME|GF_10TH,
+ 0,
+ },
+
+ // Myst Masterpiece Edition
+ // English Windows
+ // From clone2727
+ {
+ {
+ "myst",
+ "Masterpiece Edition",
+ AD_ENTRY1("MYST.DAT", "c4cae9f143b5947262e6cb2397e1617e"),
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_MYST,
+ GF_ME|GF_10TH,
+ 0,
+ },
+
+ // Myst Masterpiece Edition
+ // German Windows
+ // From DrMcCoy (Included in "Myst: Die Trilogie")
+ {
+ {
+ "myst",
+ "Masterpiece Edition",
+ AD_ENTRY1("MYST.DAT", "f88e0ace66dbca78eebdaaa1d3314ceb"),
+ Common::DE_DEU,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_MYST,
+ GF_ME,
+ 0,
+ },
+
+ // Riven: The Sequel to Myst
+ // Version 1.0 (5CD)
+ // From clone2727
+ {
+ {
+ "riven",
+ "",
+ AD_ENTRY1("a_Data.MHK", "71145fdecbd68a0cfc292c2fbddf8e08"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_RIVEN,
+ 0,
+ 0,
+ },
+
+ // Riven: The Sequel to Myst
+ // Version 1.03 (5CD)
+ // From ST
+ {
+ {
+ "riven",
+ "",
+ AD_ENTRY1("a_Data.MHK", "d8ccae34a0e3c709135a73f449b783be"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_RIVEN,
+ 0,
+ 0,
+ },
+
+ // Riven: The Sequel to Myst
+ // Version 1.? (5CD)
+ // From jvprat
+ {
+ {
+ "riven",
+ "",
+ AD_ENTRY1("a_Data.MHK", "249e8c995d191b03ee94c892c0eac775"),
+ Common::ES_ESP,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_RIVEN,
+ 0,
+ 0,
+ },
+
+ // Riven: The Sequel to Myst
+ // Version 1.? (DVD, From "Myst 10th Anniversary Edition")
+ // From Clone2727
+ {
+ {
+ "riven",
+ "DVD",
+ AD_ENTRY1("a_Data.MHK", "08fcaa5d5a2a01d7a5a6960f497212fe"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_RIVEN,
+ GF_DVD|GF_10TH,
+ 0,
+ },
+
+ // Riven: The Sequel to Myst
+ // Version 1.0 (DVD, From "Myst: Die Trilogie")
+ // From DrMcCoy
+ {
+ {
+ "riven",
+ "",
+ AD_ENTRY1("a_Data.MHK", "a5fe1c91a6033eb6ee54b287578b74b9"),
+ Common::DE_DEU,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_RIVEN,
+ GF_DVD,
+ 0,
+ },
+
+ // Riven: The Sequel to Myst
+ // Version ? (Demo, From "Prince of Persia Collector's Edition")
+ // From Clone2727
+ {
+ {
+ "riven",
+ "Demo",
+ AD_ENTRY1("a_Data.MHK", "bae6b03bd8d6eb350d35fd13f0e3139f"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_DEMO,
+ Common::GUIO_NONE
+ },
+ GType_RIVEN,
+ GF_DEMO,
+ 0,
+ },
+
+#ifdef DETECT_BRODERBUND_TITLES
+ {
+ {
+ "zoombini",
+ "",
+ AD_ENTRY1("ZOOMBINI.MHK", "98b758fec55104c096cfd129048be9a6"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_ZOOMBINI,
+ GF_HASMIDI,
+ 0
+ },
+
+ {
+ {
+ "csworld",
+ "v3.0",
+ AD_ENTRY1("C2K.MHK", "605fe88380848031bbd0ff84ade6fe40"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_CSWORLD,
+ 0,
+ 0
+ },
+
+ {
+ {
+ "csworld",
+ "v3.5",
+ AD_ENTRY1("C2K.MHK", "d4857aeb0f5e2e0c4ac556aa74f38c23"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_CSWORLD,
+ 0,
+ 0
+ },
+
+ {
+ {
+ "csamtrak",
+ "",
+ AD_ENTRY1("AMTRAK.MHK", "2f95301f0bb950d555bb7b0e3b1b7eb1"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_CSAMTRAK,
+ 0,
+ 0
+ },
+
+ {
+ {
+ "maggiess",
+ "",
+ AD_ENTRY1("MAGGIESS.MHK", "08f75fc8c0390e68fdada5ddb35d0355"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_MAGGIESS,
+ 0,
+ 0
+ },
+
+ {
+ {
+ "jamesmath",
+ "",
+ AD_ENTRY1("BRODER.MHK", "007299da8b2c6e8ec1cde9598c243024"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_JAMESMATH,
+ GF_HASMIDI,
+ 0
+ },
+
+ // This is in the NEWDATA folder, so I assume it's a newer version ;)
+ {
+ {
+ "jamesmath",
+ "",
+ AD_ENTRY1("BRODER.MHK", "53c000938a50dca92860fd9b546dd276"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_JAMESMATH,
+ GF_HASMIDI,
+ 1
+ },
+
+ {
+ {
+ "treehouse",
+ "",
+ AD_ENTRY1("MAINROOM.MHK", "12f51894d7f838af639ea9bf1bc8f45b"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_TREEHOUSE,
+ GF_HASMIDI,
+ 0
+ },
+
+ {
+ {
+ "greeneggs",
+ "",
+ AD_ENTRY1("GREEN.LB", "5df8438138186f89e71299d7b4f88d06"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_NEWLIVINGBOOKS,
+ 0,
+ 0
+ },
+
+ // 32-bit version of the previous entry
+ {
+ {
+ "greeneggs",
+ "",
+ AD_ENTRY1("GREEN32.LB", "5df8438138186f89e71299d7b4f88d06"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_NEWLIVINGBOOKS,
+ 0,
+ 0
+ },
+
+ {
+ {
+ "1stdegree",
+ "",
+ AD_ENTRY1("AL236_1.MHK", "3ba145492a7b8b4dee0ef4222c5639c3"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_1STDEGREE,
+ GF_HASMIDI,
+ 0
+ },
+
+ {
+ {
+ "csusa",
+ "",
+ AD_ENTRY1("USAC2K.MHK", "b8c9d3a2586f62bce3a48b50d7a700e9"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_CSUSA,
+ 0,
+ 0
+ },
+
+ {
+ {
+ "tortoise",
+ "Demo v1.0",
+ AD_ENTRY1("TORTOISE.512", "75d9a2f8339e423604a0c6e8177600a6"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_DEMO,
+ Common::GUIO_NONE
+ },
+ GType_OLDLIVINGBOOKS,
+ GF_DEMO,
+ 0
+ },
+
+ {
+ {
+ "tortoise",
+ "Demo v1.1",
+ AD_ENTRY1("TORTOISE.512", "a38c99360e2bea3bfdec418469aef022"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_DEMO,
+ Common::GUIO_NONE
+ },
+ GType_OLDLIVINGBOOKS,
+ GF_DEMO,
+ 0
+ },
+
+ {
+ {
+ "arthur",
+ "",
+ AD_ENTRY1("PAGES.512", "1550a361454ec452fe7d2328aac2003c"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_OLDLIVINGBOOKS,
+ 0,
+ 0
+ },
+
+ {
+ {
+ "arthur",
+ "Demo",
+ AD_ENTRY1("PAGES.512", "a4d68cef197af1416921ca5b2e0c1e31"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_DEMO,
+ Common::GUIO_NONE
+ },
+ GType_OLDLIVINGBOOKS,
+ GF_DEMO,
+ 0
+ },
+
+ {
+ {
+ "arthur",
+ "Demo",
+ AD_ENTRY1("Bookoutline", "7e2691611ff4c7b89c05221736628059"),
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_DEMO,
+ Common::GUIO_NONE
+ },
+ GType_OLDLIVINGBOOKS,
+ GF_DEMO,
+ 0
+ },
+
+ {
+ {
+ "grandma",
+ "Demo v1.0",
+ AD_ENTRY1("PAGES.512", "95d9f4b035bf5d15c57a9189f231b0f8"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_DEMO,
+ Common::GUIO_NONE
+ },
+ GType_OLDLIVINGBOOKS,
+ GF_DEMO,
+ 0
+ },
+
+ {
+ {
+ "grandma",
+ "Demo v1.1",
+ AD_ENTRY1("GRANDMA.512", "72a4d5fb1b3f06b5f75425635d42ce2e"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_DEMO,
+ Common::GUIO_NONE
+ },
+ GType_OLDLIVINGBOOKS,
+ GF_DEMO,
+ 0
+ },
+
+ {
+ {
+ "grandma",
+ "Demo",
+ AD_ENTRY1("Bookoutline", "553c93891b9631d1e1d269599e1efa6c"),
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_DEMO,
+ Common::GUIO_NONE
+ },
+ GType_OLDLIVINGBOOKS,
+ GF_DEMO,
+ 0
+ },
+
+ {
+ {
+ "ruff",
+ "Demo",
+ AD_ENTRY1("RUFF.512", "2ba1aa65177c816e156db648c398d362"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_DEMO,
+ Common::GUIO_NONE
+ },
+ GType_OLDLIVINGBOOKS,
+ GF_DEMO,
+ 0
+ },
+
+ {
+ {
+ "ruff",
+ "Demo",
+ AD_ENTRY1("Ruff's Bone Demo", "22553ac2ceb2a166bdf1def6ad348532"),
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_DEMO,
+ Common::GUIO_NONE
+ },
+ GType_OLDLIVINGBOOKS,
+ GF_DEMO,
+ 0
+ },
+
+ {
+ {
+ "newkid",
+ "Demo v1.0",
+ AD_ENTRY1("NEWKID.512", "2b9d94763a50d514c04a3af488934f73"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_DEMO,
+ Common::GUIO_NONE
+ },
+ GType_OLDLIVINGBOOKS,
+ GF_DEMO,
+ 0
+ },
+
+ {
+ {
+ "newkid",
+ "Demo v1.1",
+ AD_ENTRY1("NEWKID.512", "41e975b7390c626f8d1058a34f9d9b2e"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_DEMO,
+ Common::GUIO_NONE
+ },
+ GType_OLDLIVINGBOOKS,
+ GF_DEMO,
+ 0
+ },
+
+ {
+ {
+ "arthurrace",
+ "",
+ AD_ENTRY1("RACE.LB", "1645f36bcb36e440d928e920aa48c373"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_NEWLIVINGBOOKS,
+ 0,
+ 0
+ },
+
+ // 32-bit version of the previous entry
+ {
+ {
+ "arthurrace",
+ "",
+ AD_ENTRY1("RACE32.LB", "292a05bc48c1dd9583821a4181a02ef2"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_NEWLIVINGBOOKS,
+ 0,
+ 0
+ },
+#endif
+
+ { AD_TABLE_END_MARKER, 0, 0, 0 }
+};
+
+//////////////////////////////
+//Fallback detection
+//////////////////////////////
+
+static const MohawkGameDescription fallbackDescs[] = {
+ {
+ {
+ "myst",
+ "unknown",
+ AD_ENTRY1(0, 0),
+ Common::UNK_LANG,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_MYST,
+ 0,
+ 0
+ },
+
+ {
+ {
+ "MakingOfMyst",
+ "unknown",
+ AD_ENTRY1(0, 0),
+ Common::UNK_LANG,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_MAKINGOF,
+ 0,
+ 0
+ },
+
+ // The Masterpiece Edition of Myst has 24bit color, and thus is not supported by ScummVM
+ // Currently, its graphics are just bypassed.
+ {
+ {
+ "myst",
+ "unknown (Masterpiece Edition)",
+ AD_ENTRY1(0, 0),
+ Common::UNK_LANG,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_MYST,
+ GF_ME,
+ 0
+ },
+
+ {
+ {
+ "riven",
+ "unknown",
+ AD_ENTRY1(0, 0),
+ Common::UNK_LANG,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_RIVEN,
+ 0,
+ 0
+ },
+
+ {
+ {
+ "riven",
+ "unknown (DVD)",
+ AD_ENTRY1(0, 0),
+ Common::UNK_LANG,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_RIVEN,
+ GF_DVD,
+ 0
+ }
+};
+
+static const ADFileBasedFallback fileBased[] = {
+ { &fallbackDescs[0], { "MYST.DAT", 0 } },
+ { &fallbackDescs[1], { "MAKING.DAT", 0 } },
+ { &fallbackDescs[2], { "MYST.DAT", "Help.dat", 0 } }, // Help system doesn't exist in original
+ { &fallbackDescs[3], { "a_Data.MHK", 0 } },
+ { &fallbackDescs[4], { "a_Data.MHK", "t_Data1.MHK" , 0 } },
+ { 0, { 0 } }
+};
+
+} // End of namespace Mohawk
+
+static const ADParams detectionParams = {
+ // Pointer to ADGameDescription or its superset structure
+ (const byte *)Mohawk::gameDescriptions,
+ // Size of that superset structure
+ sizeof(Mohawk::MohawkGameDescription),
+ // Number of bytes to compute MD5 sum for
+ 5000,
+ // List of all engine targets
+ mohawkGames,
+ // Structure for autoupgrading obsolete targets
+ 0,
+ // Name of single gameid (optional)
+ "mohawk",
+ // List of files for file-based fallback detection (optional)
+ Mohawk::fileBased,
+ // Flags
+ 0,
+ // Additional GUI options (for every game)
+ Common::GUIO_NONE
+};
+
+class MohawkMetaEngine : public AdvancedMetaEngine {
+public:
+ MohawkMetaEngine() : AdvancedMetaEngine(detectionParams) {}
+
+ virtual const char *getName() const {
+ return "Mohawk Engine";
+ }
+
+ virtual const char *getOriginalCopyright() const {
+ return "Myst and Riven (C) Cyan Worlds\nMohawk OS (C) Ubisoft";
+ }
+
+ 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 { return 999; }
+ virtual void removeSaveState(const char *target, int slot) const;
+};
+
+bool MohawkMetaEngine::hasFeature(MetaEngineFeature f) const {
+ return
+ (f == kSupportsListSaves)
+ || (f == kSupportsLoadingDuringStartup)
+ || (f == kSupportsDeleteSave);
+}
+
+SaveStateList MohawkMetaEngine::listSaves(const char *target) const {
+ Common::StringList filenames;
+ SaveStateList saveList;
+
+ // Loading games is only supported in Myst/Riven currently.
+ if (strstr(target, "myst")) {
+ filenames = g_system->getSavefileManager()->listSavefiles("*.mys");
+
+ for (uint32 i = 0; i < filenames.size(); i++)
+ saveList.push_back(SaveStateDescriptor(i, filenames[i]));
+ } else if (strstr(target, "riven")) {
+ filenames = g_system->getSavefileManager()->listSavefiles("*.rvn");
+
+ for (uint32 i = 0; i < filenames.size(); i++)
+ saveList.push_back(SaveStateDescriptor(i, filenames[i]));
+ }
+
+ return saveList;
+}
+
+void MohawkMetaEngine::removeSaveState(const char *target, int slot) const {
+ // Removing saved games is only supported in Myst/Riven currently.
+ if (strstr(target, "myst")) {
+ Common::StringList filenames = g_system->getSavefileManager()->listSavefiles("*.mys");
+ g_system->getSavefileManager()->removeSavefile(filenames[slot].c_str());
+ } else if (strstr(target, "riven")) {
+ Common::StringList filenames = g_system->getSavefileManager()->listSavefiles("*.rvn");
+ g_system->getSavefileManager()->removeSavefile(filenames[slot].c_str());
+ }
+}
+
+bool MohawkMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
+ const Mohawk::MohawkGameDescription *gd = (const Mohawk::MohawkGameDescription *)desc;
+
+ if (gd) {
+ switch (gd->gameType) {
+ case Mohawk::GType_MYST:
+ case Mohawk::GType_MAKINGOF:
+ *engine = new Mohawk::MohawkEngine_Myst(syst, gd);
+ break;
+ case Mohawk::GType_RIVEN:
+ *engine = new Mohawk::MohawkEngine_Riven(syst, gd);
+ break;
+ case Mohawk::GType_OLDLIVINGBOOKS:
+ case Mohawk::GType_NEWLIVINGBOOKS:
+ *engine = new Mohawk::MohawkEngine_LivingBooks(syst, gd);
+ break;
+ case Mohawk::GType_ZOOMBINI:
+ case Mohawk::GType_CSWORLD:
+ case Mohawk::GType_CSAMTRAK:
+ case Mohawk::GType_MAGGIESS:
+ case Mohawk::GType_JAMESMATH:
+ case Mohawk::GType_TREEHOUSE:
+ case Mohawk::GType_1STDEGREE:
+ case Mohawk::GType_CSUSA:
+ error ("Unsupported Mohawk Engine");
+ break;
+ default:
+ error ("Unknown Mohawk Engine");
+ }
+ }
+
+ return (gd != 0);
+}
+
+#if PLUGIN_ENABLED_DYNAMIC(MOHAWK)
+ REGISTER_PLUGIN_DYNAMIC(MOHAWK, PLUGIN_TYPE_ENGINE, MohawkMetaEngine);
+#else
+ REGISTER_PLUGIN_STATIC(MOHAWK, PLUGIN_TYPE_ENGINE, MohawkMetaEngine);
+#endif
diff --git a/engines/mohawk/dialogs.cpp b/engines/mohawk/dialogs.cpp
new file mode 100644
index 0000000000..8757a613fa
--- /dev/null
+++ b/engines/mohawk/dialogs.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "mohawk/mohawk.h"
+#include "mohawk/myst.h"
+#include "mohawk/riven.h"
+#include "mohawk/dialogs.h"
+
+#include "gui/GuiManager.h"
+#include "common/savefile.h"
+
+namespace Mohawk {
+
+// This used to have GUI::Dialog("MohawkDummyDialog"), but that doesn't work with the gui branch merge :P (Sorry, Tanoku!)
+InfoDialog::InfoDialog(MohawkEngine *vm, Common::String message) : _vm(vm), GUI::Dialog(0, 0, 1, 1), _message(message) {
+ _backgroundType = GUI::ThemeEngine::kDialogBackgroundSpecial;
+
+ _text = new GUI::StaticTextWidget(this, 4, 4, 10, 10, _message, Graphics::kTextAlignCenter);
+}
+
+void InfoDialog::setInfoText(Common::String message) {
+ _message = message;
+ _text->setLabel(_message);
+}
+
+void InfoDialog::reflowLayout() {
+ const int screenW = g_system->getOverlayWidth();
+ const int screenH = g_system->getOverlayHeight();
+
+ int width = g_gui.getStringWidth(_message) + 16;
+ int height = g_gui.getFontHeight() + 8;
+
+ _w = width;
+ _h = height;
+ _x = (screenW - width) / 2;
+ _y = (screenH - height) / 2;
+
+ _text->setSize(_w - 8, _h);
+}
+
+PauseDialog::PauseDialog(MohawkEngine *vm, Common::String message) : InfoDialog(vm, message) {
+}
+
+void PauseDialog::handleKeyDown(Common::KeyState state) {
+ if (state.ascii == ' ')
+ close();
+ else
+ InfoDialog::handleKeyDown(state);
+}
+
+enum {
+ kCloseCmd = 'CLOS',
+ kZipCmd = 'ZIPM',
+ kTransCmd = 'TRAN',
+ kWaterCmd = 'WATR'
+};
+
+MystOptionsDialog::MystOptionsDialog(MohawkEngine_Myst* vm) : GUI::OptionsDialog("", 120, 120, 360, 200), _vm(vm) {
+ _zipModeCheckbox = new GUI::CheckboxWidget(this, 15, 10, 300, 15, "Zip Mode Activated", kZipCmd, 'Z');
+ _transistionsCheckbox = new GUI::CheckboxWidget(this, 15, 30, 300, 15, "Transistions Enabled", kTransCmd, 'T');
+
+ new GUI::ButtonWidget(this, 95, 160, 120, 25, "OK", GUI::OptionsDialog::kOKCmd, 'O');
+ new GUI::ButtonWidget(this, 225, 160, 120, 25, "Cancel", kCloseCmd, 'C');
+}
+
+MystOptionsDialog::~MystOptionsDialog() {
+}
+
+void MystOptionsDialog::open() {
+ Dialog::open();
+
+ _zipModeCheckbox->setState(_vm->_zipMode);
+ _transistionsCheckbox->setState(_vm->_transitionsEnabled);
+}
+
+void MystOptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
+ switch (cmd) {
+ case kZipCmd:
+ _vm->_zipMode = _zipModeCheckbox->getState();
+ break;
+ case kTransCmd:
+ _vm->_transitionsEnabled = _transistionsCheckbox->getState();
+ break;
+ case kCloseCmd:
+ close();
+ break;
+ default:
+ GUI::OptionsDialog::handleCommand(sender, cmd, data);
+ }
+}
+
+RivenOptionsDialog::RivenOptionsDialog(MohawkEngine_Riven* vm) : GUI::OptionsDialog("", 120, 120, 360, 200), _vm(vm) {
+ _zipModeCheckbox = new GUI::CheckboxWidget(this, 15, 10, 300, 15, "Zip Mode Activated", kZipCmd, 'Z');
+ _waterEffectCheckbox = new GUI::CheckboxWidget(this, 15, 30, 300, 15, "Water Effect Enabled", kWaterCmd, 'W');
+
+ new GUI::ButtonWidget(this, 95, 160, 120, 25, "OK", GUI::OptionsDialog::kOKCmd, 'O');
+ new GUI::ButtonWidget(this, 225, 160, 120, 25, "Cancel", kCloseCmd, 'C');
+}
+
+RivenOptionsDialog::~RivenOptionsDialog() {
+}
+
+void RivenOptionsDialog::open() {
+ Dialog::open();
+
+ _zipModeCheckbox->setState(*_vm->matchVarToString("azip") != 0);
+ _waterEffectCheckbox->setState(*_vm->matchVarToString("waterenabled") != 0);
+}
+
+void RivenOptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
+ switch (cmd) {
+ case kZipCmd:
+ *_vm->matchVarToString("azip") = _zipModeCheckbox->getState() ? 1 : 0;
+ break;
+ case kWaterCmd:
+ *_vm->matchVarToString("waterenabled") = _waterEffectCheckbox->getState() ? 1 : 0;
+ break;
+ case kCloseCmd:
+ close();
+ break;
+ default:
+ GUI::OptionsDialog::handleCommand(sender, cmd, data);
+ }
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/dialogs.h b/engines/mohawk/dialogs.h
new file mode 100644
index 0000000000..5b3aad2446
--- /dev/null
+++ b/engines/mohawk/dialogs.h
@@ -0,0 +1,100 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MOHAWK_DIALOGS_H
+#define MOHAWK_DIALOGS_H
+
+#include "mohawk/mohawk.h"
+
+#include "common/events.h"
+#include "common/str.h"
+#include "gui/dialog.h"
+#include "gui/options.h"
+#include "gui/widget.h"
+#include "gui/ListWidget.h"
+
+namespace Mohawk {
+
+class MohawkEngine;
+
+class InfoDialog : public GUI::Dialog {
+protected:
+ MohawkEngine *_vm;
+ Common::String _message;
+ GUI::StaticTextWidget *_text;
+
+public:
+ InfoDialog(MohawkEngine *vm, Common::String message);
+
+ void setInfoText(Common::String message);
+
+ virtual void handleMouseDown(int x, int y, int button, int clickCount) {
+ setResult(0);
+ close();
+ }
+
+ virtual void handleKeyDown(Common::KeyState state) {
+ setResult(state.ascii);
+ close();
+ }
+
+ virtual void reflowLayout();
+};
+
+class PauseDialog : public InfoDialog {
+public:
+ PauseDialog(MohawkEngine* vm, Common::String message);
+ virtual void handleKeyDown(Common::KeyState state);
+};
+
+class MystOptionsDialog : public GUI::OptionsDialog {
+public:
+ MystOptionsDialog(MohawkEngine_Myst *vm);
+ ~MystOptionsDialog();
+ void open();
+
+ virtual void handleCommand(GUI::CommandSender*, uint32, uint32);
+private:
+ MohawkEngine_Myst *_vm;
+ GUI::CheckboxWidget *_zipModeCheckbox;
+ GUI::CheckboxWidget *_transistionsCheckbox;
+};
+
+class RivenOptionsDialog : public GUI::OptionsDialog {
+public:
+ RivenOptionsDialog(MohawkEngine_Riven *vm);
+ ~RivenOptionsDialog();
+ void open();
+
+ virtual void handleCommand(GUI::CommandSender*, uint32, uint32);
+private:
+ MohawkEngine_Riven *_vm;
+ GUI::CheckboxWidget *_zipModeCheckbox;
+ GUI::CheckboxWidget *_waterEffectCheckbox;
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/file.cpp b/engines/mohawk/file.cpp
new file mode 100644
index 0000000000..bca9ef6008
--- /dev/null
+++ b/engines/mohawk/file.cpp
@@ -0,0 +1,317 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "mohawk/file.h"
+
+#include "common/util.h"
+
+namespace Mohawk {
+
+MohawkFile::MohawkFile() {
+ _mhk = NULL;
+ _curFile = Common::String::emptyString;
+ _types = NULL;
+ _fileTable = NULL;
+}
+
+void MohawkFile::open(Common::String filename) {
+ Common::File *file = new Common::File();
+ if (!file->open(filename.c_str()))
+ error ("Could not open file \'%s\'", filename.c_str());
+
+ _curFile = filename;
+
+ open(file);
+}
+
+void MohawkFile::close() {
+ delete _mhk; _mhk = NULL;
+ delete[] _types; _types = NULL;
+ delete[] _fileTable; _fileTable = NULL;
+
+ _curFile = Common::String::emptyString;
+}
+
+void MohawkFile::open(Common::SeekableReadStream *stream) {
+ // Make sure no other file is open...
+ close();
+ _mhk = stream;
+
+ if (_mhk->readUint32BE() != ID_MHWK)
+ error ("Could not find tag \'MHWK\'");
+
+ _fileSize = _mhk->readUint32BE();
+
+ if (_mhk->readUint32BE() != ID_RSRC)
+ error ("Could not find tag \'RSRC\'");
+
+ _rsrc.size = _mhk->readUint32BE();
+ _rsrc.filesize = _mhk->readUint32BE();
+ _rsrc.abs_offset = _mhk->readUint32BE();
+ _rsrc.file_table_offset = _mhk->readUint16BE();
+ _rsrc.file_table_size = _mhk->readUint16BE();
+
+ debug (3, "Absolute Offset = %08x", _rsrc.abs_offset);
+
+ /////////////////////////////////
+ //Resource Dir
+ /////////////////////////////////
+
+ // Type Table
+ _mhk->seek(_rsrc.abs_offset);
+ _typeTable.name_offset = _mhk->readUint16BE();
+ _typeTable.resource_types = _mhk->readUint16BE();
+
+ debug (0, "Name List Offset = %04x Number of Resource Types = %04x", _typeTable.name_offset, _typeTable.resource_types);
+
+ _types = new Type[_typeTable.resource_types];
+
+ for (uint16 i = 0; i < _typeTable.resource_types; i++) {
+ _types[i].tag = _mhk->readUint32BE();
+ _types[i].resource_table_offset = _mhk->readUint16BE();
+ _types[i].name_table_offset = _mhk->readUint16BE();
+
+ // HACK: Zoombini's SND resource starts will a NULL.
+ if (_types[i].tag == ID_SND)
+ debug (3, "Type[%02d]: Tag = \'SND\' ResTable Offset = %04x NameTable Offset = %04x", i, _types[i].resource_table_offset, _types[i].name_table_offset);
+ else
+ debug (3, "Type[%02d]: Tag = \'%s\' ResTable Offset = %04x NameTable Offset = %04x", i, tag2str(_types[i].tag), _types[i].resource_table_offset, _types[i].name_table_offset);
+
+ //Resource Table
+ _mhk->seek(_rsrc.abs_offset + _types[i].resource_table_offset);
+ _types[i].resTable.resources = _mhk->readUint16BE();
+
+ debug (3, "Resources = %04x", _types[i].resTable.resources);
+
+ _types[i].resTable.entries = new Type::ResourceTable::Entries[_types[i].resTable.resources];
+
+ for (uint16 j = 0; j < _types[i].resTable.resources; j++) {
+ _types[i].resTable.entries[j].id = _mhk->readUint16BE();
+ _types[i].resTable.entries[j].index = _mhk->readUint16BE();
+
+ debug (4, "Entry[%02x]: ID = %04x (%d) Index = %04x", j, _types[i].resTable.entries[j].id, _types[i].resTable.entries[j].id, _types[i].resTable.entries[j].index);
+ }
+
+ // Name Table
+ _mhk->seek(_rsrc.abs_offset + _types[i].name_table_offset);
+ _types[i].nameTable.num = _mhk->readUint16BE();
+
+ debug (3, "Names = %04x", _types[i].nameTable.num);
+
+ _types[i].nameTable.entries = new Type::NameTable::Entries[_types[i].nameTable.num];
+
+ for (uint16 j = 0; j < _types[i].nameTable.num; j++) {
+ _types[i].nameTable.entries[j].offset = _mhk->readUint16BE();
+ _types[i].nameTable.entries[j].index = _mhk->readUint16BE();
+
+ debug (4, "Entry[%02x]: Name List Offset = %04x Index = %04x", j, _types[i].nameTable.entries[j].offset, _types[i].nameTable.entries[j].index);
+
+ // Name List
+ uint32 pos = _mhk->pos();
+ _mhk->seek(_rsrc.abs_offset + _typeTable.name_offset + _types[i].nameTable.entries[j].offset);
+ char c = (char)_mhk->readByte();
+ while (c != 0) {
+ _types[i].nameTable.entries[j].name += c;
+ c = (char)_mhk->readByte();
+ }
+
+ debug (3, "Name = \'%s\'", _types[i].nameTable.entries[j].name.c_str());
+
+ // Get back to next entry
+ _mhk->seek(pos);
+ }
+
+ // Return to next TypeTable entry
+ _mhk->seek(_rsrc.abs_offset + (i + 1) * 8 + 4);
+
+ debug (3, "\n");
+ }
+
+ _mhk->seek(_rsrc.abs_offset + _rsrc.file_table_offset);
+ _fileTableAmount = _mhk->readUint32BE();
+ _fileTable = new FileTable[_fileTableAmount];
+
+ for (uint32 i = 0; i < _fileTableAmount; i++) {
+ _fileTable[i].offset = _mhk->readUint32BE();
+ _fileTable[i].dataSize = _mhk->readUint16BE();
+ _fileTable[i].dataSize += _mhk->readByte() << 16; // Get bits 15-24 of dataSize too
+ _fileTable[i].flags = _mhk->readByte();
+ _fileTable[i].unk = _mhk->readUint16BE();
+
+ // Add in another 3 bits for file size from the flags.
+ // The flags are useless to us except for doing this ;)
+ _fileTable[i].dataSize += (_fileTable[i].flags & 7) << 24;
+
+ debug (4, "File[%02x]: Offset = %08x DataSize = %07x Flags = %02x Unk = %04x", i, _fileTable[i].offset, _fileTable[i].dataSize, _fileTable[i].flags, _fileTable[i].unk);
+ }
+}
+
+bool MohawkFile::hasResource(uint32 tag, uint16 id) {
+ if (!_mhk)
+ return false;
+
+ int16 typeIndex = getTypeIndex(tag);
+
+ if (typeIndex < 0)
+ return false;
+
+ return (getIdIndex(typeIndex, id) >= 0);
+}
+
+Common::SeekableReadStream *MohawkFile::getRawData(uint32 tag, uint16 id) {
+ if (!_mhk)
+ error ("MohawkFile::getRawData - No File in Use");
+
+ int16 typeIndex = getTypeIndex(tag);
+
+ if (typeIndex < 0)
+ error ("Could not find a tag of \'%s\' in file \'%s\'", tag2str(tag), _curFile.c_str());
+
+ int16 idIndex = getIdIndex(typeIndex, id);
+
+ if (idIndex < 0)
+ error ("Could not find \'%s\' %04x in file \'%s\'", tag2str(tag), id, _curFile.c_str());
+
+ // Note: the fileTableIndex is based off 1, not 0. So, subtract 1
+ uint16 fileTableIndex = _types[typeIndex].resTable.entries[idIndex].index - 1;
+
+ // WORKAROUND: tMOV resources pretty much ignore the size part of the file table,
+ // as the original just passed the full Mohawk file to QuickTime and the offset.
+ // We need to do this because of the way Mohawk is set up (this is much more "proper"
+ // than passing _mhk at the right offset). We may want to do that in the future, though.
+ if (_types[typeIndex].tag == ID_TMOV) {
+ if (fileTableIndex == _fileTableAmount)
+ return new Common::SeekableSubReadStream(_mhk, _fileTable[fileTableIndex].offset, _mhk->size());
+ else
+ return new Common::SeekableSubReadStream(_mhk, _fileTable[fileTableIndex].offset, _fileTable[fileTableIndex + 1].offset);
+ }
+
+ return new Common::SeekableSubReadStream(_mhk, _fileTable[fileTableIndex].offset, _fileTable[fileTableIndex].offset + _fileTable[fileTableIndex].dataSize);
+}
+
+void OldMohawkFile::open(Common::SeekableReadStream *stream) {
+ close();
+ _mhk = stream;
+
+ // This is for the "old" Mohawk resource format used in some older
+ // Living Books. It is very similar, just missing the MHWK tag and
+ // some other minor differences, especially with the file table
+ // being merged into the resource table.
+
+ uint32 headerSize = _mhk->readUint32BE();
+
+ // NOTE: There are differences besides endianness! (Subtle changes,
+ // but different).
+
+ if (headerSize == 6) { // We're in Big Endian mode (Macintosh)
+ _mhk->readUint16BE(); // Resource Table Size
+ _typeTable.resource_types = _mhk->readUint16BE();
+ _types = new OldType[_typeTable.resource_types];
+
+ debug (0, "Old Mohawk File (Macintosh): Number of Resource Types = %04x", _typeTable.resource_types);
+
+ for (uint16 i = 0; i < _typeTable.resource_types; i++) {
+ _types[i].tag = _mhk->readUint32BE();
+ _types[i].resource_table_offset = (uint16)_mhk->readUint32BE() + 6;
+ _mhk->readUint32BE(); // Unknown (always 0?)
+
+ debug (3, "Type[%02d]: Tag = \'%s\' ResTable Offset = %04x", i, tag2str(_types[i].tag), _types[i].resource_table_offset);
+
+ uint32 oldPos = _mhk->pos();
+
+ // Resource Table/File Table
+ _mhk->seek(_types[i].resource_table_offset);
+ _types[i].resTable.resources = _mhk->readUint16BE();
+ _types[i].resTable.entries = new OldType::ResourceTable::Entries[_types[i].resTable.resources];
+
+ for (uint16 j = 0; j < _types[i].resTable.resources; j++) {
+ _types[i].resTable.entries[j].id = _mhk->readUint16BE();
+ _types[i].resTable.entries[j].offset = _mhk->readUint32BE();
+ _types[i].resTable.entries[j].size = _mhk->readByte() << 16;
+ _types[i].resTable.entries[j].size += _mhk->readUint16BE();
+ _mhk->skip(5); // Unknown (always 0?)
+
+ debug (4, "Entry[%02x]: ID = %04x (%d)\tOffset = %08x, Size = %08x", j, _types[i].resTable.entries[j].id, _types[i].resTable.entries[j].id, _types[i].resTable.entries[j].offset, _types[i].resTable.entries[j].size);
+ }
+
+ _mhk->seek(oldPos);
+ debug (3, "\n");
+ }
+ } else if (SWAP_BYTES_32(headerSize) == 6) { // We're in Little Endian mode (Windows)
+ _mhk->readUint16LE(); // Resource Table Size
+ _typeTable.resource_types = _mhk->readUint16LE();
+ _types = new OldType[_typeTable.resource_types];
+
+ debug (0, "Old Mohawk File (Windows): Number of Resource Types = %04x", _typeTable.resource_types);
+
+ for (uint16 i = 0; i < _typeTable.resource_types; i++) {
+ _types[i].tag = _mhk->readUint32LE();
+ _types[i].resource_table_offset = _mhk->readUint16LE() + 6;
+ _mhk->readUint16LE(); // Unknown (always 0?)
+
+ debug (3, "Type[%02d]: Tag = \'%s\' ResTable Offset = %04x", i, tag2str(_types[i].tag), _types[i].resource_table_offset);
+
+ uint32 oldPos = _mhk->pos();
+
+ // Resource Table/File Table
+ _mhk->seek(_types[i].resource_table_offset);
+ _types[i].resTable.resources = _mhk->readUint16LE();
+ _types[i].resTable.entries = new OldType::ResourceTable::Entries[_types[i].resTable.resources];
+
+ for (uint16 j = 0; j < _types[i].resTable.resources; j++) {
+ _types[i].resTable.entries[j].id = _mhk->readUint16LE();
+ _types[i].resTable.entries[j].offset = _mhk->readUint32LE();
+ _types[i].resTable.entries[j].size = _mhk->readUint16LE();
+ _mhk->readUint32LE(); // Unknown (always 0?)
+
+ debug (4, "Entry[%02x]: ID = %04x (%d)\tOffset = %08x, Size = %08x", j, _types[i].resTable.entries[j].id, _types[i].resTable.entries[j].id, _types[i].resTable.entries[j].offset, _types[i].resTable.entries[j].size);
+ }
+
+ _mhk->seek(oldPos);
+ debug (3, "\n");
+ }
+ } else
+ error("Could not determine type of Old Mohawk resource");
+
+}
+
+Common::SeekableReadStream *OldMohawkFile::getRawData(uint32 tag, uint16 id) {
+ if (!_mhk)
+ error ("OldMohawkFile::getRawData - No File in Use");
+
+ int16 typeIndex = getTypeIndex(tag);
+
+ if (typeIndex < 0)
+ error ("Could not find a tag of \'%s\' in file \'%s\'", tag2str(tag), _curFile.c_str());
+
+ int16 idIndex = getIdIndex(typeIndex, id);
+
+ if (idIndex < 0)
+ error ("Could not find \'%s\' %04x in file \'%s\'", tag2str(tag), id, _curFile.c_str());
+
+ return new Common::SeekableSubReadStream(_mhk, _types[typeIndex].resTable.entries[idIndex].offset, _types[typeIndex].resTable.entries[idIndex].offset + _types[typeIndex].resTable.entries[idIndex].size);
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/file.h b/engines/mohawk/file.h
new file mode 100644
index 0000000000..2a498d0e03
--- /dev/null
+++ b/engines/mohawk/file.h
@@ -0,0 +1,257 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/scummsys.h"
+#include "common/endian.h"
+#include "common/file.h"
+#include "common/str.h"
+
+#include "mohawk/sound.h"
+
+#ifndef MOHAWK_FILE_H
+#define MOHAWK_FILE_H
+
+namespace Mohawk {
+
+// Main FourCC's
+#define ID_MHWK MKID_BE('MHWK') // Main FourCC
+#define ID_RSRC MKID_BE('RSRC') // Resource Directory Tag
+
+// Myst Resource FourCC's
+#define ID_CLRC MKID_BE('CLRC') // Cursor Hotspots
+#define ID_EXIT MKID_BE('EXIT') // Card Exit Scripts
+#define ID_HINT MKID_BE('HINT') // Specifies Cursors in What Area
+#define ID_INIT MKID_BE('INIT') // Card Entrance Scripts
+#define ID_MSND MKID_BE('MSND') // Standard Mohawk Sound
+#define ID_RLST MKID_BE('RLST') // Resource List, Specifies HotSpots
+#define ID_RSFL MKID_BE('RSFL') // ??? (system.dat only)
+#define ID_VIEW MKID_BE('VIEW') // Card Details
+#define ID_WDIB MKID_BE('WDIB') // LZ-Compressed Windows Bitmap
+
+// Myst Masterpiece Edition Resource FourCC's (In addition to Myst FourCC's)
+#define ID_HELP MKID_BE('HELP') // Help Chunk
+#define ID_MJMP MKID_BE('MJMP') // MSND Jumps (To reduce MSND duplication)
+#define ID_PICT MKID_BE('PICT') // JPEG/PICT Image
+
+// Riven Resource FourCC's
+#define ID_BLST MKID_BE('BLST') // Card Hotspot Enabling Lists
+#define ID_CARD MKID_BE('CARD') // Card Scripts
+#define ID_FLST MKID_BE('FLST') // Card SFXE Lists
+#define ID_HSPT MKID_BE('HSPT') // Card Hotspots
+#define ID_MLST MKID_BE('MLST') // Card Movie Lists
+#define ID_NAME MKID_BE('NAME') // Object Names
+#define ID_PLST MKID_BE('PLST') // Card Picture Lists
+#define ID_RMAP MKID_BE('RMAP') // Card Code
+#define ID_SFXE MKID_BE('SFXE') // Water Effect Animations
+#define ID_SLST MKID_BE('SLST') // Card Ambient Sound Lists
+#define ID_TMOV MKID_BE('tMOV') // Game Movie
+
+// Riven Saved Game FourCC's
+#define ID_VARS MKID_BE('VARS') // Saved Game Variable Values
+#define ID_VERS MKID_BE('VERS') // Version Info
+#define ID_ZIPS MKID_BE('ZIPS') // Zip Mode Status
+
+// Zoombini Resource FourCC's
+#define ID_SND MKID_BE('\0SND') // Standard Mohawk Sound
+#define ID_CURS MKID_BE('CURS') // Cursor?
+#define ID_SCRB MKID_BE('SCRB') // ???
+#define ID_SCRS MKID_BE('SCRS') // ???
+#define ID_NODE MKID_BE('NODE') // ???
+#define ID_PATH MKID_BE('PATH') // ???
+#define ID_SHPL MKID_BE('SHPL') // ???
+
+// Living Books Resource FourCC's
+#define ID_TCUR MKID_BE('tCUR') // Cursor
+#define ID_BITL MKID_BE('BITL') // ???
+#define ID_CTBL MKID_BE('CTBL') // Color Table?
+#define ID_SCRP MKID_BE('SCRP') // Script?
+#define ID_SPR MKID_BE('SPR#') // Sprites?
+#define ID_VRSN MKID_BE('VRSN') // Version
+#define ID_ANI MKID_BE('ANI ') // Animation?
+#define ID_SHP MKID_BE('SHP#') // ???
+
+// JamesMath Resource FourCC's
+#define ID_TANM MKID_BE('tANM') // Animation?
+#define ID_TMFO MKID_BE('tMFO') // ???
+
+// Mohawk Wave Tags
+#define ID_WAVE MKID_BE('WAVE') // Game Sound (Third Tag)
+#define ID_ADPC MKID_BE('ADPC') // Game Sound Chunk
+#define ID_DATA MKID_BE('Data') // Game Sound Chunk
+#define ID_CUE MKID_BE('Cue#') // Game Sound Chunk
+
+// Mohawk MIDI Tags
+#define ID_MIDI MKID_BE('MIDI') // Game Sound (Third Tag), instead of WAVE
+#define ID_PRG MKID_BE('Prg#') // Midi Program?
+
+// Old Mohawk Resource FourCC's
+#define ID_WAV MKID_BE('WAV ') // Old Sound Resource
+#define ID_BMAP MKID_BE('BMAP') // Standard Mohawk Bitmap
+
+// Common Resource FourCC's
+#define ID_TBMP MKID_BE('tBMP') // Standard Mohawk Bitmap
+#define ID_TWAV MKID_BE('tWAV') // Standard Mohawk Sound
+#define ID_TPAL MKID_BE('tPAL') // Standard Mohawk Palette
+#define ID_TCNT MKID_BE('tCNT') // ??? (CSWorld, CSAmtrak, JamesMath)
+#define ID_TSCR MKID_BE('tSCR') // Script? Screen? (CSWorld, CSAmtrak, Treehouse)
+#define ID_STRL MKID_BE('STRL') // String List (Zoombini, CSWorld, CSAmtrak)
+#define ID_TBMH MKID_BE('tBMH') // Standard Mohawk Bitmap
+#define ID_TMID MKID_BE('tMID') // Standard Mohawk MIDI
+#define ID_REGS MKID_BE('REGS') // ??? (Zoombini, Treehouse)
+#define ID_BYTS MKID_BE('BYTS') // Database Entry (CSWorld, CSAmtrak)
+#define ID_INTS MKID_BE('INTS') // ??? (CSWorld, CSAmtrak)
+#define ID_BBOX MKID_BE('BBOX') // Boxes? (CSWorld, CSAmtrak)
+#define ID_SYSX MKID_BE('SYSX') // MIDI Sysex
+
+struct FileTable {
+ uint32 offset;
+ uint32 dataSize; // Really 27 bits
+ byte flags; // Mostly useless except for the bottom 3 bits which are part of the size
+ uint16 unk; // Always 0
+};
+
+struct Type {
+ Type() { resTable.entries = NULL; nameTable.entries = NULL; }
+ ~Type() { delete[] resTable.entries; delete[] nameTable.entries; }
+
+ //Type Table
+ uint32 tag;
+ uint16 resource_table_offset;
+ uint16 name_table_offset;
+
+ struct ResourceTable {
+ uint16 resources;
+ struct Entries {
+ uint16 id;
+ uint16 index;
+ } *entries;
+ } resTable;
+
+ struct NameTable {
+ uint16 num;
+ struct Entries {
+ uint16 offset;
+ uint16 index;
+ // Name List
+ Common::String name;
+ } *entries;
+ } nameTable;
+};
+
+struct TypeTable {
+ uint16 name_offset;
+ uint16 resource_types;
+};
+
+struct RSRC_Header {
+ uint32 size;
+ uint32 filesize;
+ uint32 abs_offset;
+ uint16 file_table_offset;
+ uint16 file_table_size;
+};
+
+class MohawkFile {
+public:
+ MohawkFile();
+ virtual ~MohawkFile() { close(); }
+
+ void open(Common::String filename);
+ virtual void open(Common::SeekableReadStream *stream);
+ void close();
+
+ bool hasResource(uint32 tag, uint16 id);
+ virtual Common::SeekableReadStream *getRawData(uint32 tag, uint16 id);
+
+protected:
+ Common::SeekableReadStream *_mhk;
+ TypeTable _typeTable;
+ Common::String _curFile;
+
+private:
+ bool _hasData;
+ uint32 _fileSize;
+ RSRC_Header _rsrc;
+ Type *_types;
+ FileTable *_fileTable;
+ uint16 _nameTableAmount;
+ uint16 _resourceTableAmount;
+ uint16 _fileTableAmount;
+
+ virtual int16 getTypeIndex(uint32 tag) {
+ for (uint16 i = 0; i < _typeTable.resource_types; i++)
+ if (_types[i].tag == tag)
+ return i;
+ return -1; // not found
+ }
+
+ virtual int16 getIdIndex(int16 typeIndex, uint16 id) {
+ for (uint16 i = 0; i < _types[typeIndex].resTable.resources; i++)
+ if (_types[typeIndex].resTable.entries[i].id == id)
+ return i;
+ return -1; // not found
+ }
+};
+
+class OldMohawkFile : public MohawkFile {
+public:
+ OldMohawkFile() : MohawkFile() {}
+ ~OldMohawkFile() {}
+
+ void open(Common::SeekableReadStream *stream);
+ Common::SeekableReadStream *getRawData(uint32 tag, uint16 id);
+
+private:
+ struct OldType {
+ uint32 tag;
+ uint16 resource_table_offset;
+ struct ResourceTable {
+ uint16 resources;
+ struct Entries {
+ uint16 id;
+ uint32 offset;
+ uint32 size;
+ } *entries;
+ } resTable;
+ } *_types;
+
+ int16 getTypeIndex(uint32 tag) {
+ for (uint16 i = 0; i < _typeTable.resource_types; i++)
+ if (_types[i].tag == tag)
+ return i;
+ return -1; // not found
+ }
+
+ int16 getIdIndex(int16 typeIndex, uint16 id) {
+ for (uint16 i = 0; i < _types[typeIndex].resTable.resources; i++)
+ if (_types[typeIndex].resTable.entries[i].id == id)
+ return i;
+ return -1; // not found
+ }
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/graphics.cpp b/engines/mohawk/graphics.cpp
new file mode 100644
index 0000000000..ee9fe702de
--- /dev/null
+++ b/engines/mohawk/graphics.cpp
@@ -0,0 +1,739 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "mohawk/file.h"
+#include "mohawk/graphics.h"
+#include "mohawk/myst.h"
+#include "mohawk/riven.h"
+#include "mohawk/riven_cursors.h"
+
+#include "graphics/cursorman.h"
+#include "graphics/primitives.h"
+#include "gui/message.h"
+
+namespace Mohawk {
+
+Graphics::Surface *ImageData::getSurface() {
+ Graphics::PixelFormat pixelFormat = g_system->getScreenFormat();
+ Graphics::Surface *surface = new Graphics::Surface();
+ surface->create(_surface->w, _surface->h, pixelFormat.bytesPerPixel);
+
+ if (_surface->bytesPerPixel == 1) {
+ assert(_palette);
+
+ for (uint16 i = 0; i < _surface->h; i++) {
+ for (uint16 j = 0; j < _surface->w; j++) {
+ byte palIndex = *((byte *)_surface->pixels + i * _surface->pitch + j);
+ byte r = _palette[palIndex * 4];
+ byte g = _palette[palIndex * 4 + 1];
+ byte b = _palette[palIndex * 4 + 2];
+ if (pixelFormat.bytesPerPixel == 2)
+ *((uint16 *)surface->getBasePtr(j, i)) = pixelFormat.RGBToColor(r, g, b);
+ else
+ *((uint32 *)surface->getBasePtr(j, i)) = pixelFormat.RGBToColor(r, g, b);
+ }
+ }
+ } else
+ memcpy(surface->pixels, _surface->pixels, _surface->w * _surface->h * _surface->bytesPerPixel);
+
+ return surface;
+}
+
+MystGraphics::MystGraphics(MohawkEngine_Myst* vm) : _vm(vm) {
+ _bmpDecoder = new MystBitmap();
+
+ // The original version of Myst could run in 8bpp color too.
+ // However, it dithered videos to 8bpp and they looked considerably
+ // worse (than they already did :P). So we're not even going to
+ // support 8bpp mode in Myst (Myst ME required >8bpp anyway).
+ initGraphics(544, 333, true, NULL); // What an odd screen size!
+
+ _pixelFormat = _vm->_system->getScreenFormat();
+
+ if (_pixelFormat.bytesPerPixel == 1)
+ error("Myst requires greater than 256 colors to run");
+
+ if (_vm->getFeatures() & GF_ME) {
+ _jpegDecoder = new MystJPEG();
+ _pictDecoder = new MystPICT(_jpegDecoder);
+ } else {
+ _jpegDecoder = NULL;
+ _pictDecoder = NULL;
+ }
+
+ _pictureFile.entries = NULL;
+}
+
+MystGraphics::~MystGraphics() {
+ delete _bmpDecoder;
+ delete _jpegDecoder;
+ delete _pictDecoder;
+}
+
+static const char* picFileNames[] = {
+ "CHpics",
+ "",
+ "DUpics",
+ "INpics",
+ "MEpics",
+ "MYpics",
+ "SEpics",
+ "STpics",
+ ""
+};
+
+void MystGraphics::loadExternalPictureFile(uint16 stack) {
+ if (_vm->getPlatform() != Common::kPlatformMacintosh)
+ return;
+
+ if (_pictureFile.picFile.isOpen())
+ _pictureFile.picFile.close();
+ delete[] _pictureFile.entries;
+
+ if (!scumm_stricmp(picFileNames[stack], ""))
+ return;
+
+ if (!_pictureFile.picFile.open(picFileNames[stack]))
+ error ("Could not open external picture file \'%s\'", picFileNames[stack]);
+
+ _pictureFile.pictureCount = _pictureFile.picFile.readUint32BE();
+ _pictureFile.entries = new PictureFile::PictureEntry[_pictureFile.pictureCount];
+
+ for (uint32 i = 0; i < _pictureFile.pictureCount; i++) {
+ _pictureFile.entries[i].offset = _pictureFile.picFile.readUint32BE();
+ _pictureFile.entries[i].size = _pictureFile.picFile.readUint32BE();
+ _pictureFile.entries[i].id = _pictureFile.picFile.readUint16BE();
+ _pictureFile.entries[i].type = _pictureFile.picFile.readUint16BE();
+ _pictureFile.entries[i].width = _pictureFile.picFile.readUint16BE();
+ _pictureFile.entries[i].height = _pictureFile.picFile.readUint16BE();
+ }
+}
+
+void MystGraphics::copyImageSectionToScreen(uint16 image, Common::Rect src, Common::Rect dest) {
+ // Clip the destination rect to the screen
+ if (dest.right > _vm->_system->getWidth() || dest.bottom > _vm->_system->getHeight())
+ dest.debugPrint(4, "Clipping destination rect to the screen:");
+
+ dest.right = CLIP<int>(dest.right, 0, _vm->_system->getWidth());
+ dest.bottom = CLIP<int>(dest.bottom, 0, _vm->_system->getHeight());
+
+ Graphics::Surface *surface = NULL;
+
+
+ // Myst ME uses JPEG/PICT images instead of compressed Windows Bitmaps for room images,
+ // though there are a few weird ones that use that format. For further nonsense with images,
+ // the Macintosh version stores images in external "picture files." We check them before
+ // going to check for a PICT resource.
+ if (_vm->getFeatures() & GF_ME && _vm->getPlatform() == Common::kPlatformMacintosh && _pictureFile.picFile.isOpen()) {
+ for (uint32 i = 0; i < _pictureFile.pictureCount; i++)
+ if (_pictureFile.entries[i].id == image) {
+ if (_pictureFile.entries[i].type == 0)
+ surface = _jpegDecoder->decodeImage(new Common::SeekableSubReadStream(&_pictureFile.picFile, _pictureFile.entries[i].offset, _pictureFile.entries[i].offset + _pictureFile.entries[i].size));
+ else if (_pictureFile.entries[i].type == 1)
+ surface = _pictDecoder->decodeImage(new Common::SeekableSubReadStream(&_pictureFile.picFile, _pictureFile.entries[i].offset, _pictureFile.entries[i].offset + _pictureFile.entries[i].size));
+ else
+ error ("Unknown Picture File type %d", _pictureFile.entries[i].type);
+ break;
+ }
+ }
+
+ // We're not using the external Mac files, so it's time to delve into the main Mohawk
+ // archives. However, we still don't know if it's a PICT or WDIB resource. If it's Myst
+ // ME it's most likely a PICT, and if it's original it's definitely a WDIB. However,
+ // Myst ME throws us another curve ball in that PICT resources can contain WDIB's instead
+ // of PICT's.
+ if (!surface) {
+ bool isPict = false;
+ Common::SeekableReadStream *dataStream = NULL;
+
+ if (_vm->getFeatures() & GF_ME && _vm->hasResource(ID_PICT, image)) {
+ // The PICT resource exists. However, it could still contain a MystBitmap
+ // instead of a PICT image...
+ dataStream = _vm->getRawData(ID_PICT, image);
+
+ // Here we detect whether it's really a PICT or a WDIB. Since a MystBitmap
+ // would be compressed, there's no way to detect for the BM without a hack.
+ // So, we search for the PICT version opcode for detection.
+ dataStream->seek(512 + 10); // 512 byte pict header
+ isPict = (dataStream->readUint32BE() == 0x001102FF);
+ dataStream->seek(0);
+ } else // No PICT, so the WDIB must exist. Let's go grab it.
+ dataStream = _vm->getRawData(ID_WDIB, image);
+
+ if (isPict)
+ surface = _pictDecoder->decodeImage(dataStream);
+ else {
+ ImageData *imageData = _bmpDecoder->decodeImage(dataStream);
+ surface = imageData->getSurface();
+ delete imageData;
+ }
+ }
+
+ debug(3, "Image Blit:");
+ debug(3, "src.x: %d", src.left);
+ debug(3, "src.y: %d", src.top);
+ debug(3, "dest.x: %d", dest.left);
+ debug(3, "dest.y: %d", dest.top);
+ debug(3, "width: %d", src.width());
+ debug(3, "height: %d", src.height());
+
+ if (surface) {
+ uint16 width = MIN<int>(surface->w, dest.width());
+ uint16 height = MIN<int>(surface->h, dest.height());
+ _vm->_system->copyRectToScreen((byte *)surface->getBasePtr(src.left, src.top), surface->pitch, dest.left, dest.top, width, height);
+ surface->free();
+ delete surface;
+ }
+
+ // FIXME: Remove this and update only at certain points
+ _vm->_system->updateScreen();
+}
+
+void MystGraphics::copyImageToScreen(uint16 image, Common::Rect dest) {
+ copyImageSectionToScreen(image, Common::Rect(0, 0, 544, 333), dest);
+}
+
+void MystGraphics::showCursor(void) {
+ CursorMan.showMouse(true);
+ _vm->_needsUpdate = true;
+}
+
+void MystGraphics::hideCursor(void) {
+ CursorMan.showMouse(false);
+ _vm->_needsUpdate = true;
+}
+
+void MystGraphics::changeCursor(uint16 cursor) {
+ // Both Myst and Myst ME use the "MystBitmap" format for cursor images.
+ ImageData *data = _bmpDecoder->decodeImage(_vm->getRawData(ID_WDIB, cursor));
+ Common::SeekableReadStream *clrcStream = _vm->getRawData(ID_CLRC, cursor);
+ uint16 hotspotX = clrcStream->readUint16LE();
+ uint16 hotspotY = clrcStream->readUint16LE();
+ delete clrcStream;
+
+ // Myst ME stores some cursors as 24bpp images instead of 8bpp
+ if (data->_surface->bytesPerPixel == 1) {
+ CursorMan.replaceCursor((byte *)data->_surface->pixels, data->_surface->w, data->_surface->h, hotspotX, hotspotY, 0);
+ CursorMan.replaceCursorPalette(data->_palette, 0, 256);
+ } else
+ CursorMan.replaceCursor((byte *)data->_surface->pixels, data->_surface->w, data->_surface->h, hotspotX, hotspotY, 0xFFFFFFFF, 1, &_pixelFormat);
+
+ _vm->_needsUpdate = true;
+}
+
+void MystGraphics::drawRect(Common::Rect rect, bool active) {
+ // Useful with debugging. Shows where hotspots are on the screen and whether or not they're active.
+ if (rect.left < 0 || rect.top < 0 || rect.right > 544 || rect.bottom > 333 || !rect.isValidRect() || rect.width() == 0 || rect.height() == 0)
+ return;
+
+ Graphics::Surface *screen = _vm->_system->lockScreen();
+
+ if (active)
+ screen->frameRect(rect, _pixelFormat.RGBToColor(0, 255, 0));
+ else
+ screen->frameRect(rect, _pixelFormat.RGBToColor(255, 0, 0));
+
+ _vm->_system->unlockScreen();
+}
+
+RivenGraphics::RivenGraphics(MohawkEngine_Riven* vm) : _vm(vm) {
+ _bitmapDecoder = new MohawkBitmap();
+
+ // Give me the best you've got!
+ initGraphics(608, 436, true, NULL);
+ _pixelFormat = _vm->_system->getScreenFormat();
+
+ if (_pixelFormat.bytesPerPixel == 1)
+ error("Riven requires greater than 256 colors to run");
+
+ // The actual game graphics only take up the first 392 rows. The inventory
+ // occupies the rest of the screen and we don't use the buffer to hold that.
+ _mainScreen = new Graphics::Surface();
+ _mainScreen->create(608, 392, _pixelFormat.bytesPerPixel);
+
+ _updatesEnabled = true;
+ _scheduledTransition = -1; // no transition
+ _dirtyScreen = false;
+ _inventoryDrawn = false;
+}
+
+RivenGraphics::~RivenGraphics() {
+ _mainScreen->free();
+ delete _mainScreen;
+ delete _bitmapDecoder;
+}
+
+void RivenGraphics::copyImageToScreen(uint16 image, uint32 left, uint32 top, uint32 right, uint32 bottom) {
+ // First, decode the image and get the high color surface
+ ImageData *imageData = _bitmapDecoder->decodeImage(_vm->getRawData(ID_TBMP, image));
+ Graphics::Surface *surface = imageData->getSurface();
+ delete imageData;
+
+ // Clip the width to fit on the screen. Fixes some images.
+ if (left + surface->w > 608)
+ surface->w = 608 - left;
+
+ for (uint16 i = 0; i < surface->h; i++)
+ memcpy(_mainScreen->getBasePtr(left, i + top), surface->getBasePtr(0, i), surface->w * surface->bytesPerPixel);
+
+ surface->free();
+ delete surface;
+
+ _dirtyScreen = true;
+}
+
+void RivenGraphics::drawPLST(uint16 x) {
+ Common::SeekableReadStream* plst = _vm->getRawData(ID_PLST, _vm->getCurCard());
+ uint16 index, id, left, top, right, bottom;
+ uint16 recordCount = plst->readUint16BE();
+
+ for (uint16 i = 0; i < recordCount; i++) {
+ index = plst->readUint16BE();
+ id = plst->readUint16BE();
+ left = plst->readUint16BE();
+ top = plst->readUint16BE();
+ right = plst->readUint16BE();
+ bottom = plst->readUint16BE();
+
+ // We are also checking here to make sure we haven't drawn the image yet on screen.
+ // This fixes problems with drawing PLST 1 twice and some other images twice. PLST
+ // 1 is sometimes not called by the scripts, so some cards don't appear if we don't
+ // draw PLST 1 each time. This "hack" is here to catch any PLST attempting to draw
+ // twice. There should never be a problem with doing it this way.
+ if (index == x && !(Common::find(_activatedPLSTs.begin(), _activatedPLSTs.end(), x) != _activatedPLSTs.end())) {
+ debug (0, "Drawing image %d", id);
+ copyImageToScreen(id, left, top, right, bottom);
+ _activatedPLSTs.push_back(x);
+ break;
+ }
+ }
+
+ delete plst;
+}
+
+void RivenGraphics::updateScreen() {
+ if (_updatesEnabled) {
+ _vm->runUpdateScreenScript();
+
+ if (_dirtyScreen) {
+ _activatedPLSTs.clear();
+
+ // Copy to screen if there's no transition. Otherwise transition. ;)
+ if (_scheduledTransition < 0)
+ _vm->_system->copyRectToScreen((byte *)_mainScreen->pixels, _mainScreen->pitch, 0, 0, _mainScreen->w, _mainScreen->h);
+ else
+ runScheduledTransition();
+
+ // Finally, update the screen.
+ _vm->_system->updateScreen();
+ _dirtyScreen = false;
+ }
+ }
+}
+
+void RivenGraphics::scheduleWaterEffect(uint16 sfxeID) {
+ Common::SeekableReadStream *sfxeStream = _vm->getRawData(ID_SFXE, sfxeID);
+
+ if (sfxeStream->readUint16BE() != 'SL')
+ error ("Unknown sfxe tag");
+
+ // Read in header info
+ SFXERecord sfxeRecord;
+ sfxeRecord.frameCount = sfxeStream->readUint16BE();
+ uint32 offsetTablePosition = sfxeStream->readUint32BE();
+ sfxeRecord.rect.left = sfxeStream->readUint16BE();
+ sfxeRecord.rect.top = sfxeStream->readUint16BE();
+ sfxeRecord.rect.right = sfxeStream->readUint16BE();
+ sfxeRecord.rect.bottom = sfxeStream->readUint16BE();
+ sfxeRecord.speed = sfxeStream->readUint16BE();
+ // Skip the rest of the fields...
+
+ // Read in offsets
+ sfxeStream->seek(offsetTablePosition);
+ uint32 *frameOffsets = new uint32[sfxeRecord.frameCount];
+ for (uint16 i = 0; i < sfxeRecord.frameCount; i++)
+ frameOffsets[i] = sfxeStream->readUint32BE();
+ sfxeStream->seek(frameOffsets[0]);
+
+ // Read in the scripts
+ for (uint16 i = 0; i < sfxeRecord.frameCount; i++)
+ sfxeRecord.frameScripts.push_back(sfxeStream->readStream((i == sfxeRecord.frameCount - 1) ? sfxeStream->size() - frameOffsets[i] : frameOffsets[i + 1] - frameOffsets[i]));
+
+ // Set it to the first frame
+ sfxeRecord.curFrame = 0;
+ sfxeRecord.lastFrameTime = 0;
+
+ delete[] frameOffsets;
+ delete sfxeStream;
+ _waterEffects.push_back(sfxeRecord);
+}
+
+void RivenGraphics::clearWaterEffects() {
+ _waterEffects.clear();
+}
+
+bool RivenGraphics::runScheduledWaterEffects() {
+ // Don't run the effect if it's disabled
+ if (*_vm->matchVarToString("waterenabled") == 0)
+ return false;
+
+ Graphics::Surface *screen = NULL;
+
+ for (uint16 i = 0; i < _waterEffects.size(); i++) {
+ if (_vm->_system->getMillis() > _waterEffects[i].lastFrameTime + 1000 / _waterEffects[i].speed) {
+ // Lock the screen!
+ if (!screen)
+ screen = _vm->_system->lockScreen();
+
+ // Make sure the script is at the starting point
+ Common::SeekableReadStream *script = _waterEffects[i].frameScripts[_waterEffects[i].curFrame];
+ if (script->pos() != 0)
+ script->seek(0);
+
+ // Run script
+ uint16 curRow = 0;
+ for (uint16 op = script->readUint16BE(); op != 4; op = script->readUint16BE()) {
+ if (op == 1) { // Increment Row
+ curRow++;
+ } else if (op == 3) { // Copy Pixels
+ uint16 dstLeft = script->readUint16BE();
+ uint16 srcLeft = script->readUint16BE();
+ uint16 srcTop = script->readUint16BE();
+ uint16 rowWidth = script->readUint16BE();
+ memcpy ((byte *)screen->getBasePtr(dstLeft, curRow + _waterEffects[i].rect.top), (byte *)_mainScreen->getBasePtr(srcLeft, srcTop), rowWidth * _pixelFormat.bytesPerPixel);
+ } else if (op != 4) { // End of Script
+ error ("Unknown SFXE opcode %d", op);
+ }
+ }
+
+ // Increment frame
+ _waterEffects[i].curFrame++;
+ if (_waterEffects[i].curFrame == _waterEffects[i].frameCount)
+ _waterEffects[i].curFrame = 0;
+
+ // Set the new time
+ _waterEffects[i].lastFrameTime = _vm->_system->getMillis();
+ }
+ }
+
+ // Unlock the screen if it has been locked and return true to update the screen
+ if (screen) {
+ _vm->_system->unlockScreen();
+ return true;
+ }
+
+ return false;
+}
+
+void RivenGraphics::scheduleTransition(uint16 id, Common::Rect rect) {
+ _scheduledTransition = id;
+ _transitionRect = rect;
+}
+
+void RivenGraphics::runScheduledTransition() {
+ if (_scheduledTransition < 0) // No transition is scheduled
+ return;
+
+ // TODO: There's a lot to be done here...
+
+ // Note: Transitions 0-11 are actual transitions, but none are used in-game.
+ // There's no point in implementing them if they're not used. These extra
+ // transitions were found by hacking scripts.
+
+ switch (_scheduledTransition) {
+ case 12: // Pan Left
+ warning ("STUB: Pan left");
+ break;
+ case 13: // Pan Right
+ warning ("STUB: Pan right");
+ break;
+ case 14: // Pan Up
+ warning ("STUB: Pan up");
+ break;
+ case 15: // Pan Down
+ warning ("STUB: Pan down");
+ break;
+ case 16: // Dissolve
+ case 17: // Dissolve (tspit CARD 155)
+ warning ("STUB: Dissolve");
+ break;
+ default:
+ if (_scheduledTransition < 12)
+ error ("Found unused transition %d", _scheduledTransition);
+ else
+ error ("Found unknown transition %d", _scheduledTransition);
+ }
+
+ // For now, just copy the image to screen without doing any transition.
+ _vm->_system->copyRectToScreen((byte *)_mainScreen->pixels, _mainScreen->pitch, 0, 0, _mainScreen->w, _mainScreen->h);
+ _vm->_system->updateScreen();
+
+ _scheduledTransition = -1; // Clear scheduled transition
+}
+
+// TODO: Marble Cursors/Palettes
+void RivenGraphics::changeCursor(uint16 num) {
+ // All of Riven's cursors are hardcoded. See riven_cursors.h for these definitions.
+
+ switch (num) {
+ case 1002:
+ // Zip Mode
+ CursorMan.replaceCursor(zipModeCursor, 16, 16, 8, 8, 0);
+ CursorMan.replaceCursorPalette(zipModeCursorPalette, 1, ARRAYSIZE(zipModeCursorPalette) / 4);
+ break;
+ case 2003:
+ // Hand Over Object
+ CursorMan.replaceCursor(objectHandCursor, 16, 16, 8, 8, 0);
+ CursorMan.replaceCursorPalette(handCursorPalette, 1, ARRAYSIZE(handCursorPalette) / 4);
+ break;
+ case 2004:
+ // Grabbing/Using Object
+ CursorMan.replaceCursor(grabbingHandCursor, 13, 13, 6, 6, 0);
+ CursorMan.replaceCursorPalette(handCursorPalette, 1, ARRAYSIZE(handCursorPalette) / 4);
+ break;
+ case 3000:
+ // Standard Hand
+ CursorMan.replaceCursor(standardHandCursor, 15, 16, 6, 0, 0);
+ CursorMan.replaceCursorPalette(handCursorPalette, 1, ARRAYSIZE(handCursorPalette) / 4);
+ break;
+ case 3001:
+ // Pointing Left
+ CursorMan.replaceCursor(pointingLeftCursor, 15, 13, 0, 3, 0);
+ CursorMan.replaceCursorPalette(handCursorPalette, 1, ARRAYSIZE(handCursorPalette) / 4);
+ break;
+ case 3002:
+ // Pointing Right
+ CursorMan.replaceCursor(pointingRightCursor, 15, 13, 14, 3, 0);
+ CursorMan.replaceCursorPalette(handCursorPalette, 1, ARRAYSIZE(handCursorPalette) / 4);
+ break;
+ case 3003:
+ // Pointing Down (Palm Up)
+ CursorMan.replaceCursor(pointingDownCursorPalmUp, 13, 16, 3, 15, 0);
+ CursorMan.replaceCursorPalette(handCursorPalette, 1, ARRAYSIZE(handCursorPalette) / 4);
+ break;
+ case 3004:
+ // Pointing Up (Palm Up)
+ CursorMan.replaceCursor(pointingUpCursorPalmUp, 13, 16, 3, 0, 0);
+ CursorMan.replaceCursorPalette(handCursorPalette, 1, ARRAYSIZE(handCursorPalette) / 4);
+ break;
+ case 3005:
+ // Pointing Left (Curved)
+ CursorMan.replaceCursor(pointingLeftCursorBent, 15, 13, 0, 5, 0);
+ CursorMan.replaceCursorPalette(handCursorPalette, 1, ARRAYSIZE(handCursorPalette) / 4);
+ break;
+ case 3006:
+ // Pointing Right (Curved)
+ CursorMan.replaceCursor(pointingRightCursorBent, 15, 13, 14, 5, 0);
+ CursorMan.replaceCursorPalette(handCursorPalette, 1, ARRAYSIZE(handCursorPalette) / 4);
+ break;
+ case 3007:
+ // Pointing Down (Palm Down)
+ CursorMan.replaceCursor(pointingDownCursorPalmDown, 15, 16, 7, 15, 0);
+ CursorMan.replaceCursorPalette(handCursorPalette, 1, ARRAYSIZE(handCursorPalette) / 4);
+ break;
+ case 4001:
+ // Red Marble
+ break;
+ case 4002:
+ // Orange Marble
+ break;
+ case 4003:
+ // Yellow Marble
+ break;
+ case 4004:
+ // Green Marble
+ break;
+ case 4005:
+ // Blue Marble
+ break;
+ case 4006:
+ // Purple Marble
+ break;
+ case 5000:
+ // Pellet
+ CursorMan.replaceCursor(pelletCursor, 8, 8, 4, 4, 0);
+ CursorMan.replaceCursorPalette(pelletCursorPalette, 1, ARRAYSIZE(pelletCursorPalette) / 4);
+ break;
+ case 9000:
+ // Hide Cursor
+ CursorMan.showMouse(false);
+ break;
+ default:
+ error ("Cursor %d does not exist!", num);
+ }
+
+ if (num != 9000) // Show Cursor
+ CursorMan.showMouse(true);
+
+ // Should help in cases where we need to hide the cursor immediately.
+ _vm->_system->updateScreen();
+}
+
+void RivenGraphics::showInventory() {
+ // Don't redraw the inventory
+ if (_inventoryDrawn)
+ return;
+
+ // Clear the inventory area
+ clearInventoryArea();
+
+ // The demo doesn't have the inventory system and we don't want
+ // to show the inventory on setup screens or in other journals.
+ if (_vm->getFeatures() & GF_DEMO || _vm->getCurStack() == aspit)
+ return;
+
+ // There are three books and three vars. However, there's only
+ // a possible two combinations. Either you have only Atrus'
+ // journal or you have all three books.
+ // bool hasAtrusBook = *_vm->matchVarToString("aatrusbook") != 0;
+ bool hasCathBook = *_vm->matchVarToString("acathbook") != 0;
+ // bool hasTrapBook = *_vm->matchVarToString("atrapbook") != 0;
+
+ if (!hasCathBook) {
+ drawInventoryImage(101, atrusJournalRectSolo);
+ } else {
+ drawInventoryImage(101, atrusJournalRect);
+ drawInventoryImage(102, cathJournalRect);
+ drawInventoryImage(100, trapBookRect);
+ }
+
+ _vm->_system->updateScreen();
+ _inventoryDrawn = true;
+}
+
+void RivenGraphics::hideInventory() {
+ // Don't hide the inventory twice
+ if (!_inventoryDrawn)
+ return;
+
+ // Clear the area
+ clearInventoryArea();
+
+ _inventoryDrawn = false;
+}
+
+void RivenGraphics::clearInventoryArea() {
+ // Clear the inventory area
+ static const Common::Rect inventoryRect = Common::Rect(0, 392, 608, 436);
+
+ // Lock the screen
+ Graphics::Surface *screen = _vm->_system->lockScreen();
+
+ // Fill the inventory area with black
+ screen->fillRect(inventoryRect, _pixelFormat.RGBToColor(0, 0, 0));
+
+ _vm->_system->unlockScreen();
+}
+
+void RivenGraphics::drawInventoryImage(uint16 id, Common::Rect rect) {
+ ImageData *imageData = _bitmapDecoder->decodeImage(_vm->getExtrasResource(ID_TBMP, id));
+ Graphics::Surface *surface = imageData->getSurface();
+ delete imageData;
+
+ _vm->_system->copyRectToScreen((byte *)surface->pixels, surface->pitch, rect.left, rect.top, surface->w, surface->h);
+
+ surface->free();
+ delete surface;
+}
+
+void RivenGraphics::drawRect(Common::Rect rect, bool active) {
+ // Useful with debugging. Shows where hotspots are on the screen and whether or not they're active.
+ Graphics::Surface *screen = _vm->_system->lockScreen();
+
+ if (active)
+ screen->frameRect(rect, _pixelFormat.RGBToColor(0, 255, 0));
+ else
+ screen->frameRect(rect, _pixelFormat.RGBToColor(255, 0, 0));
+
+ _vm->_system->unlockScreen();
+}
+
+LBGraphics::LBGraphics(MohawkEngine_LivingBooks *vm) : _vm(vm) {
+ _bmpDecoder = (_vm->getGameType() == GType_OLDLIVINGBOOKS) ? new OldMohawkBitmap() : new MohawkBitmap();
+ _palette = new byte[256 * 4];
+ memset(_palette, 0, 256 * 4);
+}
+
+LBGraphics::~LBGraphics() {
+ delete _bmpDecoder;
+ delete[] _palette;
+}
+
+void LBGraphics::copyImageToScreen(uint16 image, uint16 left, uint16 right) {
+ if (_vm->getGameType() == GType_OLDLIVINGBOOKS) {
+ // Drawing images in the old format isn't supported (yet)
+ ImageData *imageData = _bmpDecoder->decodeImage(_vm->wrapStreamEndian(ID_BMAP, image));
+ delete imageData;
+ } else {
+ ImageData *imageData = _bmpDecoder->decodeImage(_vm->getRawData(ID_TBMP, image));
+ imageData->_palette = _palette;
+ Graphics::Surface *surface = imageData->getSurface();
+ imageData->_palette = NULL; // Unset the palette so it doesn't get deleted
+ delete imageData;
+
+ uint16 width = MIN<int>(surface->w, 640);
+ uint16 height = MIN<int>(surface->h, 480);
+ _vm->_system->copyRectToScreen((byte *)surface->pixels, surface->pitch, left, right, width, height);
+ surface->free();
+ delete surface;
+
+ // FIXME: Remove this and update only at certain points
+ _vm->_system->updateScreen();
+ }
+}
+
+void LBGraphics::setPalette(uint16 id) {
+ // Old Living Books gamnes use the old CTBL-style palette format while newer
+ // games use the better tPAL format which can store partial palettes.
+
+ if (_vm->getGameType() == GType_OLDLIVINGBOOKS) {
+ Common::SeekableSubReadStreamEndian *ctblStream = _vm->wrapStreamEndian(ID_CTBL, id);
+ uint16 colorCount = ctblStream->readUint16();
+
+ for (uint16 i = 0; i < colorCount; i++) {
+ _palette[i * 4] = ctblStream->readByte();
+ _palette[i * 4 + 1] = ctblStream->readByte();
+ _palette[i * 4 + 2] = ctblStream->readByte();
+ _palette[i * 4 + 3] = ctblStream->readByte();
+ }
+
+ delete ctblStream;
+ } else {
+ Common::SeekableReadStream *tpalStream = _vm->getRawData(ID_TPAL, id);
+ uint16 colorStart = tpalStream->readUint16BE();
+ uint16 colorCount = tpalStream->readUint16BE();
+
+ for (uint16 i = colorStart; i < colorStart + colorCount; i++) {
+ _palette[i * 4] = tpalStream->readByte();
+ _palette[i * 4 + 1] = tpalStream->readByte();
+ _palette[i * 4 + 2] = tpalStream->readByte();
+ _palette[i * 4 + 3] = tpalStream->readByte();
+ }
+
+ delete tpalStream;
+ }
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/graphics.h b/engines/mohawk/graphics.h
new file mode 100644
index 0000000000..be3a951148
--- /dev/null
+++ b/engines/mohawk/graphics.h
@@ -0,0 +1,202 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MOHAWK_GRAPHICS_H
+#define MOHAWK_GRAPHICS_H
+
+#include "mohawk/bitmap.h"
+#include "mohawk/livingbooks.h"
+#include "mohawk/myst_jpeg.h"
+#include "mohawk/myst_pict.h"
+
+#include "common/file.h"
+
+namespace Mohawk {
+
+class MohawkEngine_Myst;
+class MohawkEngine_Riven;
+class MohawkBitmap;
+class MystBitmap;
+
+enum {
+ kRivenMainCursor = 3000,
+ kRivenPelletCursor = 5000
+};
+
+// 803-805 are animated, one large bmp which is in chunks
+// Other cursors (200, 300, 400, 500, 600, 700) are not the same in each stack
+enum {
+ kDefaultMystCursor = 100, // The default hand
+ kWhitePageCursor = 800, // Holding a white page
+ kRedPageCursor = 801, // Holding a red page
+ kBluePageCursor = 802, // Holding a blue page
+ // kDroppingWhitePageAnimCursor = 803,
+ // kDroppingRedPageAnimCursor = 804,
+ // kDroppingBluePageAnimCursor = 805,
+ kNewMatchCursor = 900, // Match that has not yet been lit
+ kLitMatchCursor = 901, // Match that's burning
+ kDeadMatchCursor = 902, // Match that's been extinguished
+ kKeyCursor = 903, // Key in Lighthouse in Stoneship
+ kRotateClockwiseCursor = 904, // Rotate gear clockwise (boiler on Myst)
+ kRotateCounterClockwiseCursor = 905, // Rotate gear counter clockwise (boiler on Myst)
+ kMystZipModeCursor = 999 // Zip Mode cursor
+};
+
+// A simple struct to hold necessary image info
+class ImageData {
+public:
+ ImageData() : _surface(0), _palette(0) {}
+ ImageData(Graphics::Surface *surface, byte *palette = NULL) : _surface(surface), _palette(palette) {}
+ ~ImageData() {
+ if (_palette)
+ free(_palette);
+ if (_surface) {
+ _surface->free();
+ delete _surface;
+ }
+ }
+
+ // getSurface() will convert to the current screen format, if it's not already
+ // in that format. Makes it easy to support both 8bpp and 24bpp images.
+ Graphics::Surface *getSurface();
+
+ // These are still public in case the 8bpp surface needs to be accessed
+ Graphics::Surface *_surface;
+ byte *_palette;
+};
+
+class MystGraphics {
+public:
+ MystGraphics(MohawkEngine_Myst*);
+ ~MystGraphics();
+
+ void loadExternalPictureFile(uint16 stack);
+ void copyImageSectionToScreen(uint16 image, Common::Rect src, Common::Rect dest);
+ void copyImageToScreen(uint16 image, Common::Rect dest);
+ void showCursor(void);
+ void hideCursor(void);
+ void changeCursor(uint16);
+
+ void drawRect(Common::Rect rect, bool active);
+private:
+ MohawkEngine_Myst *_vm;
+ MystBitmap *_bmpDecoder;
+ MystPICT *_pictDecoder;
+ MystJPEG *_jpegDecoder;
+ Graphics::PixelFormat _pixelFormat;
+
+ struct PictureFile {
+ uint32 pictureCount;
+ struct PictureEntry {
+ uint32 offset;
+ uint32 size;
+ uint16 id;
+ uint16 type;
+ uint16 width;
+ uint16 height;
+ } *entries;
+
+ Common::File picFile;
+ } _pictureFile;
+};
+
+struct SFXERecord {
+ // Record values
+ uint16 frameCount;
+ Common::Rect rect;
+ uint16 speed;
+ Common::Array<Common::SeekableReadStream*> frameScripts;
+
+ // Cur frame
+ uint16 curFrame;
+ uint32 lastFrameTime;
+};
+
+class RivenGraphics {
+public:
+ RivenGraphics(MohawkEngine_Riven *vm);
+ ~RivenGraphics();
+
+ void copyImageToScreen(uint16, uint32, uint32, uint32, uint32);
+ void updateScreen();
+ bool _updatesEnabled;
+ void changeCursor(uint16);
+ Common::Array<uint16> _activatedPLSTs;
+ void drawPLST(uint16 x);
+ void drawRect(Common::Rect rect, bool active);
+
+ // Water Effect
+ void scheduleWaterEffect(uint16);
+ void clearWaterEffects();
+ bool runScheduledWaterEffects();
+
+ // Transitions
+ void scheduleTransition(uint16 id, Common::Rect rect = Common::Rect(0, 0, 608, 392));
+ void runScheduledTransition();
+
+ // Inventory
+ void showInventory();
+ void hideInventory();
+
+private:
+ MohawkEngine_Riven *_vm;
+ MohawkBitmap *_bitmapDecoder;
+
+ // Water Effects
+ Common::Array<SFXERecord> _waterEffects;
+
+ // Transitions
+ int16 _scheduledTransition;
+ Common::Rect _transitionRect;
+
+ // Inventory
+ void clearInventoryArea();
+ void drawInventoryImage(uint16 id, Common::Rect rect);
+ bool _inventoryDrawn;
+
+ // Screen Related
+ Graphics::Surface *_mainScreen;
+ bool _dirtyScreen;
+ Graphics::PixelFormat _pixelFormat;
+ byte findBlackIndex();
+};
+
+class LBGraphics {
+public:
+ LBGraphics(MohawkEngine_LivingBooks *vm);
+ ~LBGraphics();
+
+ void copyImageToScreen(uint16 image, uint16 left = 0, uint16 top = 0);
+ void setPalette(uint16 id);
+
+private:
+ MohawkBitmap *_bmpDecoder;
+ MohawkEngine_LivingBooks *_vm;
+ byte *_palette;
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/livingbooks.cpp b/engines/mohawk/livingbooks.cpp
new file mode 100644
index 0000000000..390a40cea9
--- /dev/null
+++ b/engines/mohawk/livingbooks.cpp
@@ -0,0 +1,324 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "mohawk/livingbooks.h"
+#include "mohawk/file.h"
+
+namespace Mohawk {
+
+MohawkEngine_LivingBooks::MohawkEngine_LivingBooks(OSystem *syst, const MohawkGameDescription *gamedesc) : MohawkEngine(syst, gamedesc) {
+ _needsUpdate = false;
+ _screenWidth = _screenHeight = 0;
+}
+
+MohawkEngine_LivingBooks::~MohawkEngine_LivingBooks() {
+ delete _console;
+ delete _gfx;
+ _bookInfoFile.clear();
+}
+
+Common::Error MohawkEngine_LivingBooks::run() {
+ MohawkEngine::run();
+
+ _console = new LivingBooksConsole(this);
+ _gfx = new LBGraphics(this);
+
+ // Load the book info from the detected file
+ loadBookInfo(getBookInfoFileName());
+
+ if (!_title.empty()) // Some games don't have the title stored
+ debug("Starting Living Books Title \'%s\'", _title.c_str());
+ if (!_copyright.empty())
+ debug("Copyright: %s", _copyright.c_str());
+
+ if (!_screenWidth || !_screenHeight)
+ error("Could not find xRes/yRes variables");
+
+ debug("Setting screen size to %dx%d", _screenWidth, _screenHeight);
+
+ // TODO: Eventually move this to a LivingBooksGraphics class or similar
+ initGraphics(_screenWidth, _screenHeight, true, NULL);
+
+ loadIntro();
+
+ debug(1, "Stack Version: %d", getResourceVersion());
+
+ _gfx->setPalette(1000);
+ loadSHP(1000);
+ loadANI(1000);
+
+ // Code to Load Sounds For Debugging...
+ //for (byte i = 0; i < 30; i++)
+ // _sound->playSound(1000+i);
+
+ Common::Event event;
+ while (!shouldQuit()) {
+ while (_eventMan->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_MOUSEMOVE:
+ break;
+ case Common::EVENT_LBUTTONUP:
+ break;
+ case Common::EVENT_LBUTTONDOWN:
+ break;
+ case Common::EVENT_KEYDOWN:
+ switch (event.kbd.keycode) {
+ case Common::KEYCODE_d:
+ if (event.kbd.flags & Common::KBD_CTRL) {
+ _console->attach();
+ _console->onFrame();
+ }
+ break;
+ case Common::KEYCODE_SPACE:
+ pauseGame();
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (_needsUpdate) {
+ _system->updateScreen();
+ _needsUpdate = false;
+ }
+
+ // Cut down on CPU usage
+ _system->delayMillis(10);
+ }
+
+ return Common::kNoError;
+}
+
+void MohawkEngine_LivingBooks::loadBookInfo(Common::String filename) {
+ if (!_bookInfoFile.loadFromFile(filename))
+ error("Could not open %s as a config file", filename.c_str());
+
+ _title = getStringFromConfig("BookInfo", "title");
+ _copyright = getStringFromConfig("BookInfo", "copyright");
+
+ _numPages = getIntFromConfig("BookInfo", "nPages");
+ _numLanguages = getIntFromConfig("BookInfo", "nLanguages");
+ _screenWidth = getIntFromConfig("BookInfo", "xRes");
+ _screenHeight = getIntFromConfig("BookInfo", "yRes");
+ // nColors is here too, but it's always 256 anyway...
+
+ // The later Living Books games add some more options:
+ // - fNeedPalette (always true?)
+ // - fUse254ColorPalette (always true?)
+ // - nKBRequired (4096, RAM requirement?)
+ // - fDebugWindow (always 0?)
+}
+
+void MohawkEngine_LivingBooks::loadIntro() {
+ Common::String filename;
+
+ // We get to try for a few different names! Yay!
+ filename = getFileNameFromConfig("Intro", "Page1");
+
+ // Some store with .r, not sure why.
+ if (filename.empty())
+ filename = getFileNameFromConfig("Intro", "Page1.r");
+
+ if (!filename.empty() && Common::File::exists(filename)) {
+ MohawkFile *introFile = createMohawkFile();
+ introFile->open(filename);
+ _mhk.push_back(introFile);
+ }
+
+ filename = getFileNameFromConfig("Intro", "Page2");
+
+ if (filename.empty())
+ filename = getFileNameFromConfig("Intro", "Page2.r");
+
+ if (!filename.empty() && Common::File::exists(filename)) {
+ MohawkFile *coverFile = createMohawkFile();
+ coverFile->open(filename);
+ _mhk.push_back(coverFile);
+ }
+}
+
+// Only 1 VSRN resource per stack, Id 1000
+uint16 MohawkEngine_LivingBooks::getResourceVersion() {
+ Common::SeekableReadStream *versionStream = getRawData(ID_VRSN, 1000);
+
+ if (versionStream->size() != 2)
+ warning("Version Record size mismatch");
+
+ uint16 version = versionStream->readUint16BE();
+
+ delete versionStream;
+ return version;
+}
+
+// Multiple SHP# resource per stack.. Optional per Card?
+// This record appears to be a list structure of BMAP resource Ids..
+void MohawkEngine_LivingBooks::loadSHP(uint16 resourceId) {
+ Common::SeekableSubReadStreamEndian *shpStream = wrapStreamEndian(ID_SHP, resourceId);
+
+ if (shpStream->size() < 6)
+ warning("SHP Record size too short");
+
+ if (shpStream->readUint16() != 3)
+ warning("SHP Record u0 not 3");
+
+ if (shpStream->readUint16() != 0)
+ warning("SHP Record u1 not 0");
+
+ uint16 idCount = shpStream->readUint16();
+ debug(1, "SHP: idCount: %d", idCount);
+
+ if (shpStream->size() != (idCount * 2) + 6)
+ warning("SHP Record size mismatch");
+
+ uint16 *idValues = new uint16[idCount];
+ for (uint16 i = 0; i < idCount; i++) {
+ idValues[i] = shpStream->readUint16();
+ debug(1, "SHP: BMAP Resource Id %d: %d", i, idValues[i]);
+ }
+
+ delete[] idValues;
+ delete shpStream;
+}
+
+// Multiple ANI resource per stack.. Optional per Card?
+void MohawkEngine_LivingBooks::loadANI(uint16 resourceId) {
+ Common::SeekableSubReadStreamEndian *aniStream = wrapStreamEndian(ID_ANI, resourceId);
+
+ if (aniStream->size() != 30)
+ warning("ANI Record size mismatch");
+
+ if (aniStream->readUint16() != 1)
+ warning("ANI Record u0 not 0"); // Version?
+
+ uint16 u1 = aniStream->readUint16();
+ debug(1, "ANI u1: %d", u1);
+
+ uint16 u2 = aniStream->readUint16();
+ debug(1, "ANI u2: %d", u2);
+
+ Common::Rect u3;
+ u3.right = aniStream->readUint16();
+ u3.bottom = aniStream->readUint16();
+ u3.left = aniStream->readUint16();
+ u3.top = aniStream->readUint16();
+ debug(1, "ANI u3: (%d, %d), (%d, %d)", u3.left, u3.top, u3.right, u3.bottom);
+
+ Common::Rect u4;
+ u4.right = aniStream->readUint16();
+ u4.bottom = aniStream->readUint16();
+ u4.left = aniStream->readUint16();
+ u4.top = aniStream->readUint16();
+ debug(1, "ANI u4: (%d, %d), (%d, %d)", u4.left, u4.top, u4.right, u4.bottom);
+
+ // BMAP Id?
+ uint16 u4ResourceId = aniStream->readUint16();
+ debug(1, "ANI u4ResourceId: %d", u4ResourceId);
+
+ // Following 3 unknowns also resourceIds in Unused?
+ uint16 u5 = aniStream->readUint16();
+ debug(1, "ANI u5: %d", u5);
+ if (u5 != 0)
+ warning("ANI u5 non-zero");
+
+ uint16 u6 = aniStream->readUint16();
+ debug(1, "ANI u6: %d", u6);
+ if (u6 != 0)
+ warning("ANI u6 non-zero");
+
+ uint16 u7 = aniStream->readUint16();
+ debug(1, "ANI u7: %d", u7);
+ if (u7 != 0)
+ warning("ANI u7 non-zero");
+
+ delete aniStream;
+}
+
+Common::SeekableSubReadStreamEndian *MohawkEngine_LivingBooks::wrapStreamEndian(uint32 tag, uint16 id) {
+ Common::SeekableReadStream *dataStream = getRawData(tag, id);
+ return new Common::SeekableSubReadStreamEndian(dataStream, 0, dataStream->size(), isBigEndian(), Common::DisposeAfterUse::YES);
+}
+
+Common::String MohawkEngine_LivingBooks::getStringFromConfig(Common::String section, Common::String key) {
+ Common::String x = Common::String::emptyString;
+ _bookInfoFile.getKey(key, section, x);
+ return removeQuotesFromString(x);
+}
+
+int MohawkEngine_LivingBooks::getIntFromConfig(Common::String section, Common::String key) {
+ return atoi(getStringFromConfig(section, key).c_str());
+}
+
+Common::String MohawkEngine_LivingBooks::getFileNameFromConfig(Common::String section, Common::String key) {
+ Common::String x = getStringFromConfig(section, key);
+ return (getPlatform() == Common::kPlatformMacintosh) ? convertMacFileName(x) : convertWinFileName(x);
+}
+
+Common::String MohawkEngine_LivingBooks::removeQuotesFromString(Common::String string) {
+ // The last char isn't necessarily a quote, the line could have "fade" in it,
+ // most likely representing to fade to that page. Hopefully it really is that
+ // obvious :P
+
+ // Some versions wrap in quotations, some don't...
+ for (uint32 i = 0; i < string.size(); i++) {
+ if (string[i] == '\"') {
+ string.deleteChar(i);
+ i--;
+ }
+ }
+
+ return string;
+}
+
+Common::String MohawkEngine_LivingBooks::convertMacFileName(Common::String string) {
+ Common::String filename;
+
+ for (uint32 i = 1; i < string.size(); i++) { // First character should be ignored (another colon)
+ if (string[i] == ':')
+ filename += '/';
+ else
+ filename += string[i];
+ }
+
+ return filename;
+}
+
+Common::String MohawkEngine_LivingBooks::convertWinFileName(Common::String string) {
+ Common::String filename;
+
+ for (uint32 i = 0; i < string.size(); i++) {
+ if (string[i] == '\\')
+ filename += '/';
+ else
+ filename += string[i];
+ }
+
+ return filename;
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/livingbooks.h b/engines/mohawk/livingbooks.h
new file mode 100644
index 0000000000..3053045574
--- /dev/null
+++ b/engines/mohawk/livingbooks.h
@@ -0,0 +1,94 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MOHAWK_LIVINGBOOKS_H
+#define MOHAWK_LIVINGBOOKS_H
+
+#include "mohawk/mohawk.h"
+#include "mohawk/console.h"
+#include "mohawk/graphics.h"
+
+#include "common/config-file.h"
+
+namespace Mohawk {
+
+enum {
+ kIntroPage = 0
+};
+
+class LBGraphics;
+
+class MohawkEngine_LivingBooks : public MohawkEngine {
+protected:
+ Common::Error run();
+
+public:
+ MohawkEngine_LivingBooks(OSystem *syst, const MohawkGameDescription *gamedesc);
+ virtual ~MohawkEngine_LivingBooks();
+
+ LBGraphics *_gfx;
+ bool _needsUpdate;
+
+ Common::SeekableSubReadStreamEndian *wrapStreamEndian(uint32 tag, uint16 id);
+ GUI::Debugger *getDebugger() { return _console; }
+
+private:
+ LivingBooksConsole *_console;
+ Common::ConfigFile _bookInfoFile;
+
+ uint16 _curPage;
+ Common::String getBookInfoFileName();
+ void loadBookInfo(Common::String filename);
+ void loadIntro();
+
+ uint16 getResourceVersion();
+ void loadSHP(uint16 resourceId);
+ void loadANI(uint16 resourceId);
+
+ uint16 _screenWidth;
+ uint16 _screenHeight;
+ uint16 _numLanguages;
+ uint16 _numPages;
+ Common::String _title;
+ Common::String _copyright;
+
+ // String Manipulation Functions
+ Common::String removeQuotesFromString(Common::String string);
+ Common::String convertMacFileName(Common::String string);
+ Common::String convertWinFileName(Common::String string);
+
+ // Configuration File Functions
+ Common::String getStringFromConfig(Common::String section, Common::String key);
+ int getIntFromConfig(Common::String section, Common::String key);
+ Common::String getFileNameFromConfig(Common::String section, Common::String key);
+
+ // Platform/Version functions
+ bool isBigEndian() { return getGameType() == GType_NEWLIVINGBOOKS || getPlatform() == Common::kPlatformMacintosh; }
+ MohawkFile *createMohawkFile() { return (getGameType() == GType_NEWLIVINGBOOKS) ? new MohawkFile() : new OldMohawkFile(); }
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/module.mk b/engines/mohawk/module.mk
new file mode 100644
index 0000000000..72d9263a11
--- /dev/null
+++ b/engines/mohawk/module.mk
@@ -0,0 +1,39 @@
+MODULE := engines/mohawk
+
+MODULE_OBJS = \
+ bitmap.o \
+ console.o \
+ detection.o \
+ dialogs.o \
+ file.o \
+ graphics.o \
+ livingbooks.o \
+ mohawk.o \
+ myst.o \
+ myst_jpeg.o \
+ myst_pict.o \
+ myst_vars.o \
+ myst_saveload.o \
+ myst_scripts.o \
+ riven.o \
+ riven_external.o \
+ riven_saveload.o \
+ riven_scripts.o \
+ riven_vars.o \
+ sound.o \
+ video/cinepak.o \
+ video/qdm2.o \
+ video/qtrle.o \
+ video/qt_player.o \
+ video/rpza.o \
+ video/smc.o \
+ video/video.o
+
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_MOHAWK), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
diff --git a/engines/mohawk/mohawk.cpp b/engines/mohawk/mohawk.cpp
new file mode 100644
index 0000000000..16b542ac51
--- /dev/null
+++ b/engines/mohawk/mohawk.cpp
@@ -0,0 +1,100 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/scummsys.h"
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/events.h"
+#include "common/keyboard.h"
+
+#include "base/plugins.h"
+#include "base/version.h"
+
+#include "mohawk/mohawk.h"
+
+namespace Mohawk {
+
+MohawkEngine::MohawkEngine(OSystem *syst, const MohawkGameDescription *gamedesc) : Engine(syst), _gameDescription(gamedesc) {
+ if (!_mixer->isReady())
+ error ("Sound initialization failed");
+
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume"));
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume"));
+}
+
+MohawkEngine::~MohawkEngine() {
+ delete _sound;
+ delete _video;
+ delete _pauseDialog;
+
+ for (uint32 i = 0; i < _mhk.size(); i++)
+ delete _mhk[i];
+ _mhk.clear();
+}
+
+Common::Error MohawkEngine::run() {
+ _sound = new Sound(this);
+ _video = new VideoManager(this);
+ _pauseDialog = new PauseDialog(this, "The game is paused. Press any key to continue.");
+
+ return Common::kNoError;
+}
+
+void MohawkEngine::pauseEngineIntern(bool pause) {
+ if (pause) {
+ _video->pauseVideos();
+ _sound->pauseSound();
+ _sound->pauseSLST();
+ } else {
+ _video->resumeVideos();
+ _sound->resumeSound();
+ _sound->resumeSLST();
+ _system->updateScreen();
+ }
+}
+
+void MohawkEngine::pauseGame() {
+ runDialog(*_pauseDialog);
+}
+
+Common::SeekableReadStream *MohawkEngine::getRawData(uint32 tag, uint16 id) {
+ for (uint32 i = 0; i < _mhk.size(); i++)
+ if (_mhk[i]->hasResource(tag, id))
+ return _mhk[i]->getRawData(tag, id);
+
+ error ("Could not find a \'%s\' resource with ID %04x", tag2str(tag), id);
+
+ return 0;
+}
+
+bool MohawkEngine::hasResource(uint32 tag, uint16 id) {
+ for (uint32 i = 0; i < _mhk.size(); i++)
+ if (_mhk[i]->hasResource(tag, id))
+ return true;
+
+ return false;
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/mohawk.h b/engines/mohawk/mohawk.h
new file mode 100644
index 0000000000..c71c525707
--- /dev/null
+++ b/engines/mohawk/mohawk.h
@@ -0,0 +1,113 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MOHAWK_H
+#define MOHAWK_H
+
+#include "common/scummsys.h"
+#include "common/util.h"
+#include "common/system.h"
+#include "common/rect.h"
+#include "common/array.h"
+
+#include "engines/engine.h"
+
+#include "gui/dialog.h"
+
+#include "mohawk/console.h"
+#include "mohawk/dialogs.h"
+#include "mohawk/file.h"
+#include "mohawk/sound.h"
+#include "mohawk/video/video.h"
+
+namespace Mohawk {
+
+enum MohawkGameType {
+ GType_MYST,
+ GType_MAKINGOF,
+ GType_RIVEN,
+ GType_ZOOMBINI,
+ GType_CSWORLD,
+ GType_CSAMTRAK,
+ GType_MAGGIESS,
+ GType_JAMESMATH,
+ GType_TREEHOUSE,
+ GType_1STDEGREE,
+ GType_CSUSA,
+ GType_OLDLIVINGBOOKS,
+ GType_NEWLIVINGBOOKS
+};
+
+enum MohawkGameFeatures {
+ GF_ME = (1 << 0), // Myst Masterpiece Edition
+ GF_DVD = (1 << 1),
+ GF_10TH = (1 << 2), // 10th Anniversary
+ GF_DEMO = (1 << 3),
+ GF_HASMIDI = (1 << 4),
+ GF_HASBINK = (1 << 5)
+};
+
+struct MohawkGameDescription;
+class Sound;
+class PauseDialog;
+
+class MohawkEngine : public ::Engine {
+protected:
+ virtual Common::Error run();
+
+public:
+ MohawkEngine(OSystem *syst, const MohawkGameDescription *gamedesc);
+ virtual ~MohawkEngine();
+
+ // Detection related functions
+ const MohawkGameDescription *_gameDescription;
+ const char* getGameId() const;
+ uint32 getFeatures() const;
+ uint16 getVersion() const;
+ Common::Platform getPlatform() const;
+ uint8 getGameType();
+ Common::Language getLanguage();
+
+ bool hasFeature(EngineFeature f) const;
+
+ Sound *_sound;
+ VideoManager *_video;
+
+ Common::SeekableReadStream *getRawData(uint32, uint16);
+ bool hasResource(uint32 tag, uint16 id);
+
+ void pauseGame();
+
+protected:
+ PauseDialog *_pauseDialog;
+ void pauseEngineIntern(bool);
+
+ // An array holding the main Mohawk archives require by the games
+ Common::Array<MohawkFile*> _mhk;
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/myst.cpp b/engines/mohawk/myst.cpp
new file mode 100644
index 0000000000..6ae7d97479
--- /dev/null
+++ b/engines/mohawk/myst.cpp
@@ -0,0 +1,1540 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/config-manager.h"
+
+#include "mohawk/graphics.h"
+#include "mohawk/myst.h"
+#include "mohawk/myst_scripts.h"
+#include "mohawk/myst_saveload.h"
+
+namespace Mohawk {
+
+MohawkEngine_Myst::MohawkEngine_Myst(OSystem *syst, const MohawkGameDescription *gamedesc) : MohawkEngine(syst, gamedesc) {
+ Common::addDebugChannel(kDebugVariable, "Variable", "Track Variable Accesses");
+ Common::addDebugChannel(kDebugSaveLoad, "SaveLoad", "Track Save/Load Function");
+ Common::addDebugChannel(kDebugView, "View", "Track Card File (VIEW) Parsing");
+ Common::addDebugChannel(kDebugHint, "Hint", "Track Cursor Hints (HINT) Parsing");
+ Common::addDebugChannel(kDebugResource, "Resource", "Track Resource (RLST) Parsing");
+ Common::addDebugChannel(kDebugINIT, "Init", "Track Card Init Script (INIT) Parsing");
+ Common::addDebugChannel(kDebugEXIT, "Exit", "Track Card Exit Script (EXIT) Parsing");
+ Common::addDebugChannel(kDebugScript, "Script", "Track Script Execution");
+ Common::addDebugChannel(kDebugHelp, "Help", "Track Help File (HELP) Parsing");
+
+ _zipMode = false;
+ _transitionsEnabled = false;
+
+ // Engine tweaks
+ // Disabling this makes engine behaviour as per
+ // original, including bugs, missing bits etc. :)
+ _tweaksEnabled = true;
+
+ _currentCursor = _mainCursor = kDefaultMystCursor;
+ _showResourceRects = false;
+ _curCard = 0;
+ _needsUpdate = false;
+ _curResource = -1;
+
+ _cursorHintCount = 0;
+ _cursorHints = NULL;
+
+ _view.conditionalImageCount = 0;
+ _view.conditionalImages = NULL;
+ _view.soundList = NULL;
+ _view.soundListVolume = NULL;
+ _view.scriptResCount = 0;
+ _view.scriptResources = NULL;
+
+ _scriptParser->disableInitOpcodes();
+
+ if ((getFeatures() & GF_ME) && getPlatform() == Common::kPlatformMacintosh)
+ SearchMan.addSubDirectoryMatching(_gameDataDir, "CD Data");
+}
+
+MohawkEngine_Myst::~MohawkEngine_Myst() {
+ delete _gfx;
+ delete _console;
+ delete _scriptParser;
+ delete _varStore;
+ delete _saveLoad;
+ delete _loadDialog;
+ delete _optionsDialog;
+ delete[] _view.conditionalImages;
+ delete[] _view.scriptResources;
+ delete[] _cursorHints;
+ _resources.clear();
+}
+
+static const char *mystFiles[] = {
+ "channel.dat",
+ "credits.dat",
+ "demo.dat",
+ "dunny.dat",
+ "intro.dat",
+ "making.dat",
+ "mechan.dat",
+ "myst.dat",
+ "selen.dat",
+ "slides.dat",
+ "sneak.dat",
+ "stone.dat"
+};
+
+// Myst Hardcoded Movie Paths
+// Mechanical Stack Movie "sstairs" referenced in executable, but not used?
+
+// NOTE: cl1wg1.mov etc. found in the root directory in versions of Myst
+// Original are duplicates of those in /qtw/myst directory and thus not necessary.
+
+// The following movies are not referenced in RLST or hardcoded into the executables.
+// It is likely they are unused:
+// qtw/mech/lwrgear2.mov + lwrgears.mov: I have no idea what these are; perhaps replaced by an animated image in-game?
+// qtw/myst/gar4wbf1.mov: gar4wbf2.mov has two butterflies instead of one
+// qtw/myst/libelev.mov: libup.mov is basically the same with sound
+
+Common::String MohawkEngine_Myst::wrapMovieFilename(Common::String movieName, uint16 stack) {
+ const char* prefix;
+
+ switch (stack) {
+ case kIntroStack:
+ prefix = "intro/";
+ break;
+ case kChannelwoodStack:
+ // The Windmill videos like to hide in a different folder
+ if (movieName.contains("wmill"))
+ prefix = "channel2/";
+ else
+ prefix = "channel/";
+ break;
+ case kDniStack:
+ prefix = "dunny/";
+ break;
+ case kMechanicalStack:
+ prefix = "mech/";
+ break;
+ case kMystStack:
+ prefix = "myst/";
+ break;
+ case kSeleniticStack:
+ prefix = "selen/";
+ break;
+ case kStoneshipStack:
+ prefix = "stone/";
+ break;
+ default:
+ prefix = ""; // Masterpiece Edition Only Movies
+ break;
+ }
+
+ if ((getFeatures() & GF_ME) && getPlatform() == Common::kPlatformMacintosh)
+ return Common::String("CD Data/m/") + movieName + ".mov";
+
+ return Common::String("qtw/") + prefix + movieName + ".mov";
+}
+
+Common::Error MohawkEngine_Myst::run() {
+ MohawkEngine::run();
+
+ _gfx = new MystGraphics(this);
+ _console = new MystConsole(this);
+ _varStore = new MystVar(this);
+ _saveLoad = new MystSaveLoad(this, _saveFileMan);
+ _scriptParser = new MystScriptParser(this);
+ _loadDialog = new GUI::SaveLoadChooser("Load Game:", "Load");
+ _loadDialog->setSaveMode(false);
+ _optionsDialog = new MystOptionsDialog(this);
+
+ // Start us on the first stack.
+ if (getGameType() == GType_MAKINGOF)
+ changeToStack(kMakingOfStack);
+ else if (getFeatures() & GF_DEMO)
+ changeToStack(kDemoStack);
+ else
+ changeToStack(kIntroStack);
+
+ if (getGameType() == GType_MAKINGOF)
+ changeToCard(1);
+ else {
+ if ((getFeatures() & GF_ME) && getPlatform() == Common::kPlatformMacintosh) {
+ _video->playMovieCentered(wrapMovieFilename("mattel", kIntroStack));
+ _video->playMovieCentered(wrapMovieFilename("presto", kIntroStack));
+ } else
+ _video->playMovieCentered(wrapMovieFilename("broder", kIntroStack));
+
+ _video->playMovieCentered(wrapMovieFilename("cyanlogo", kIntroStack));
+
+ if (!(getFeatures() & GF_DEMO)) { // The demo doesn't have the intro video
+ if ((getFeatures() & GF_ME) && getPlatform() == Common::kPlatformMacintosh)
+ // intro.mov uses Sorenson, introc uses Cinepak. Otherwise, they're the same.
+ _video->playMovieCentered(wrapMovieFilename("introc", kIntroStack));
+ else
+ _video->playMovieCentered(wrapMovieFilename("intro", kIntroStack));
+ }
+
+ if (shouldQuit())
+ return Common::kNoError;
+
+ if (getFeatures() & GF_DEMO)
+ changeToCard(2001);
+ else {
+ // It should be card 1 for the full game eventually too, but it's not working
+ // there at the moment. Card 2 is the card with the book on the ground.
+ changeToCard(2);
+ }
+ }
+
+ // Load game from launcher/command line if requested
+ if (ConfMan.hasKey("save_slot") && !(getFeatures() & GF_DEMO)) {
+ uint32 gameToLoad = ConfMan.getInt("save_slot");
+ Common::StringList savedGamesList = _saveLoad->generateSaveGameList();
+ if (gameToLoad > savedGamesList.size())
+ error ("Could not find saved game");
+ _saveLoad->loadGame(savedGamesList[gameToLoad]);
+ // HACK: The save_slot variable is saved to the disk! We don't want this!
+ ConfMan.removeKey("save_slot", ConfMan.getActiveDomainName());
+ ConfMan.flushToDisk();
+ }
+
+ // Load Help System (Masterpiece Edition Only)
+ if (getFeatures() & GF_ME) {
+ MohawkFile *mhk = new MohawkFile();
+ mhk->open("help.dat");
+ _mhk.push_back(mhk);
+ }
+
+ // Test Load Function...
+ loadHelp(10000);
+
+ // Set the cursor
+ _gfx->changeCursor(_currentCursor);
+ _gfx->showCursor();
+
+ Common::Event event;
+ while (!shouldQuit()) {
+ // Update any background videos
+ _needsUpdate = _video->updateBackgroundMovies();
+ _scriptParser->runPersistentOpcodes();
+
+ // Run animations...
+ for (uint16 i = 0; i < _resources.size(); i++)
+ _resources[i]->handleAnimation();
+
+ while (_eventMan->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_MOUSEMOVE:
+ _mousePos = event.mouse;
+ _needsUpdate = true;
+ checkCurrentResource();
+ break;
+ case Common::EVENT_LBUTTONUP:
+ if (_curResource >= 0) {
+ debug(2, "Sending mouse up event to resource %d\n", _curResource);
+ _resources[_curResource]->handleMouseUp();
+ }
+
+ for (uint16 i = 0; i < _resources.size(); i++)
+ if (_resources[i]->isEnabled())
+ _resources[i]->drawDataToScreen();
+ break;
+ case Common::EVENT_LBUTTONDOWN:
+ if (_curResource >= 0) {
+ debug(2, "Sending mouse up event to resource %d\n", _curResource);
+ _resources[_curResource]->handleMouseDown();
+ }
+ break;
+ case Common::EVENT_KEYDOWN:
+ switch (event.kbd.keycode) {
+ case Common::KEYCODE_d:
+ if (event.kbd.flags & Common::KBD_CTRL) {
+ _console->attach();
+ _console->onFrame();
+ }
+ break;
+ case Common::KEYCODE_SPACE:
+ pauseGame();
+ break;
+ case Common::KEYCODE_F4:
+ _showResourceRects = !_showResourceRects;
+ if (_showResourceRects)
+ drawResourceRects();
+ break;
+ case Common::KEYCODE_F5:
+ runDialog(*_optionsDialog);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (_needsUpdate) {
+ _system->updateScreen();
+ _needsUpdate = false;
+ }
+
+ // Cut down on CPU usage
+ _system->delayMillis(10);
+ }
+
+ return Common::kNoError;
+}
+
+void MohawkEngine_Myst::changeToStack(uint16 stack) {
+ debug(2, "changeToStack(%d)", stack);
+
+ _curStack = stack;
+
+ // If the array is empty, add a new one. Otherwise, delete the first
+ // entry which is the stack file (the second, if there, is the help file).
+ if (_mhk.empty())
+ _mhk.push_back(new MohawkFile());
+ else {
+ delete _mhk[0];
+ _mhk[0] = new MohawkFile();
+ }
+
+ _mhk[0]->open(mystFiles[_curStack]);
+
+ if (getPlatform() == Common::kPlatformMacintosh)
+ _gfx->loadExternalPictureFile(_curStack);
+
+ _runExitScript = false;
+}
+
+void MohawkEngine_Myst::changeToCard(uint16 card) {
+ debug(2, "changeToCard(%d)", card);
+
+ _scriptParser->disableInitOpcodes();
+
+ _video->stopVideos();
+
+ // Run exit script from last card (if present)
+ if (_runExitScript)
+ runExitScript();
+
+ _runExitScript = true;
+
+ unloadCard();
+
+ _curCard = card;
+
+ // Load a bunch of stuff
+ loadCard();
+ loadResources();
+ loadCursorHints();
+
+ // Handle images
+ if (_view.conditionalImageCount != 0) {
+ for (uint16 i = 0; i < _view.conditionalImageCount; i++) {
+ if (_varStore->getVar(_view.conditionalImages[i].var) < _view.conditionalImages[i].numStates)
+ _gfx->copyImageToScreen(_view.conditionalImages[i].values[_varStore->getVar(_view.conditionalImages[i].var)], Common::Rect(0, 0, 544, 333));
+ else
+ warning("Conditional image %d variable %d: %d exceeds maximum state of %d", i, _view.conditionalImages[i].var, _varStore->getVar(_view.conditionalImages[i].var), _view.conditionalImages[i].numStates-1);
+ }
+ } else if (_view.mainImage != 0)
+ _gfx->copyImageToScreen(_view.mainImage, Common::Rect(0, 0, 544, 333));
+
+ // Handle sound
+ int16 soundAction = 0;
+ uint16 soundActionVolume = 0;
+
+ if (_view.sound == kMystSoundActionConditional) {
+ uint16 soundVarValue = _varStore->getVar(_view.soundVar);
+ if (soundVarValue >= _view.soundCount)
+ warning("Conditional sound variable outside range");
+ else {
+ soundAction = _view.soundList[soundVarValue];
+ soundActionVolume = _view.soundListVolume[soundVarValue];
+ }
+ } else {
+ soundAction = _view.sound;
+ soundActionVolume = _view.soundVolume;
+ }
+
+ // NOTE: Mixer only has 8-bit channel volume granularity,
+ // Myst uses 16-bit? Or is part of this balance?
+ soundActionVolume = (byte)(soundActionVolume / 255);
+
+ if (soundAction == kMystSoundActionContinue)
+ debug(2, "Continuing with current sound");
+ else if (soundAction == kMystSoundActionChangeVolume) {
+ debug(2, "Continuing with current sound, changing volume");
+ // TODO: Implement Volume Control..
+ } else if (soundAction == kMystSoundActionStop) {
+ debug(2, "Stopping sound");
+ _sound->stopSound();
+ } else if (soundAction > 0) {
+ debug(2, "Playing new sound %d", soundAction);
+ _sound->stopSound();
+ // TODO: Need to keep sound handle and add function to change volume of
+ // looped running sound for kMystSoundActionChangeVolume type
+ _sound->playSound(soundAction, true, soundActionVolume);
+ } else {
+ error("Unknown sound action %d", soundAction);
+ }
+
+ // TODO: Handle Script Resources
+
+ // Run the entrance script (if present)
+ runInitScript();
+
+ // Make sure we have the right cursor showing
+ _curResource = -1;
+ checkCurrentResource();
+
+ // Debug: Show resource rects
+ if (_showResourceRects)
+ drawResourceRects();
+}
+
+void MohawkEngine_Myst::drawResourceRects() {
+ for (uint16 i = 0; i < _resources.size(); i++) {
+ _resources[i]->getRect().debugPrint(0);
+ if (_resources[i]->getRect().isValidRect())
+ _gfx->drawRect(_resources[i]->getRect(), _resources[i]->isEnabled());
+ }
+
+ _system->updateScreen();
+}
+
+void MohawkEngine_Myst::checkCurrentResource() {
+ // See what resource we're over
+ bool foundResource = false;
+
+ for (uint16 i = 0; i < _resources.size(); i++)
+ if (_resources[i]->isEnabled() && _resources[i]->contains(_system->getEventManager()->getMousePos())) {
+ if (_curResource != i) {
+ if (_curResource != -1)
+ _resources[_curResource]->handleMouseLeave();
+ _resources[i]->handleMouseEnter();
+ }
+
+ _curResource = i;
+ foundResource = true;
+ break;
+ }
+
+ // Set the resource to none if we're not over any
+ if (!foundResource)
+ _curResource = -1;
+
+ checkCursorHints();
+}
+
+void MohawkEngine_Myst::loadCard() {
+ debugC(kDebugView, "Loading Card View:");
+
+ Common::SeekableReadStream *viewStream = getRawData(ID_VIEW, _curCard);
+
+ // Card Flags
+ _view.flags = viewStream->readUint16LE();
+ debugC(kDebugView, "Flags: 0x%04X", _view.flags);
+
+ // The Image Block (Reminiscent of Riven PLST resources)
+ _view.conditionalImageCount = viewStream->readUint16LE();
+ debugC(kDebugView, "Conditional Image Count: %d", _view.conditionalImageCount);
+ if (_view.conditionalImageCount != 0) {
+ _view.conditionalImages = new MystCondition[_view.conditionalImageCount];
+ for (uint16 i = 0; i < _view.conditionalImageCount; i++) {
+ debugC(kDebugView, "\tImage %d:", i);
+ _view.conditionalImages[i].var = viewStream->readUint16LE();
+ debugC(kDebugView, "\t\tVar: %d", _view.conditionalImages[i].var);
+ _view.conditionalImages[i].numStates = viewStream->readUint16LE();
+ debugC(kDebugView, "\t\tNumber of States: %d", _view.conditionalImages[i].numStates);
+ _view.conditionalImages[i].values = new uint16[_view.conditionalImages[i].numStates];
+ for (uint16 j = 0; j < _view.conditionalImages[i].numStates; j++) {
+ _view.conditionalImages[i].values[j] = viewStream->readUint16LE();
+ debugC(kDebugView, "\t\tState %d -> Value %d", j, _view.conditionalImages[i].values[j]);
+ }
+ }
+ _view.mainImage = 0;
+ } else {
+ _view.mainImage = viewStream->readUint16LE();
+ debugC(kDebugView, "Main Image: %d", _view.mainImage);
+ }
+
+ // The Sound Block (Reminiscent of Riven SLST resources)
+ _view.sound = viewStream->readSint16LE();
+ debugCN(kDebugView, "Sound Control: %d = ", _view.sound);
+ if (_view.sound > 0) {
+ debugC(kDebugView, "Play new Sound, change volume");
+ debugC(kDebugView, "\tSound: %d", _view.sound);
+ _view.soundVolume = viewStream->readUint16LE();
+ debugC(kDebugView, "\tVolume: %d", _view.soundVolume);
+ } else if (_view.sound == kMystSoundActionContinue)
+ debugC(kDebugView, "Continue current sound");
+ else if (_view.sound == kMystSoundActionChangeVolume) {
+ debugC(kDebugView, "Continue current sound, change volume");
+ _view.soundVolume = viewStream->readUint16LE();
+ debugC(kDebugView, "\tVolume: %d", _view.soundVolume);
+ } else if (_view.sound == kMystSoundActionStop) {
+ debugC(kDebugView, "Stop sound");
+ } else if (_view.sound == kMystSoundActionConditional) {
+ debugC(kDebugView, "Conditional sound list");
+ _view.soundVar = viewStream->readUint16LE();
+ debugC(kDebugView, "\tVar: %d", _view.soundVar);
+ _view.soundCount = viewStream->readUint16LE();
+ debugC(kDebugView, "\tCount: %d", _view.soundCount);
+ _view.soundList = new int16[_view.soundCount];
+ _view.soundListVolume = new uint16[_view.soundCount];
+
+ for (uint16 i = 0; i < _view.soundCount; i++) {
+ _view.soundList[i] = viewStream->readSint16LE();
+ debugC(kDebugView, "\t\tCondition %d: Action %d", i, _view.soundList[i]);
+ if (_view.soundList[i] == kMystSoundActionChangeVolume || _view.soundList[i] >= 0) {
+ _view.soundListVolume[i] = viewStream->readUint16LE();
+ debugC(kDebugView, "\t\tCondition %d: Volume %d", i, _view.soundListVolume[i]);
+ }
+ }
+ } else {
+ debugC(kDebugView, "Unknown");
+ warning("Unknown sound control value in card");
+ }
+
+ // Resources that scripts can call upon
+ _view.scriptResCount = viewStream->readUint16LE();
+ debugC(kDebugView, "Script Resource Count: %d", _view.scriptResCount);
+ if (_view.scriptResCount != 0) {
+ _view.scriptResources = new MystView::ScriptResource[_view.scriptResCount];
+ for (uint16 i = 0; i < _view.scriptResCount; i++) {
+ debugC(kDebugView, "\tResource %d:", i);
+ _view.scriptResources[i].type = viewStream->readUint16LE();
+ debugC(kDebugView, "\t\t Type: %d", _view.scriptResources[i].type);
+
+ switch (_view.scriptResources[i].type) {
+ case 1:
+ debugC(kDebugView, "\t\t\t\t= Image");
+ break;
+ case 2:
+ debugC(kDebugView, "\t\t\t\t= Sound");
+ break;
+ case 3:
+ debugC(kDebugView, "\t\t\t\t= Resource List");
+ break;
+ default:
+ debugC(kDebugView, "\t\t\t\t= Unknown");
+ break;
+ }
+
+ if (_view.scriptResources[i].type == 3) {
+ _view.scriptResources[i].var = viewStream->readUint16LE();
+ debugC(kDebugView, "\t\t Var: %d", _view.scriptResources[i].var);
+ _view.scriptResources[i].count = viewStream->readUint16LE();
+ debugC(kDebugView, "\t\t Resource List Count: %d", _view.scriptResources[i].count);
+ _view.scriptResources[i].u0 = viewStream->readUint16LE();
+ debugC(kDebugView, "\t\t u0: %d", _view.scriptResources[i].u0);
+ _view.scriptResources[i].resource_list = new int16[_view.scriptResources[i].count];
+
+ for (uint16 j = 0; j < _view.scriptResources[i].count; j++) {
+ _view.scriptResources[i].resource_list[j] = viewStream->readSint16LE();
+ debugC(kDebugView, "\t\t Resource List %d: %d", j, _view.scriptResources[i].resource_list[j]);
+ }
+ } else {
+ _view.scriptResources[i].resource_list = NULL;
+ _view.scriptResources[i].id = viewStream->readUint16LE();
+ debugC(kDebugView, "\t\t Id: %d", _view.scriptResources[i].id);
+ }
+ }
+ }
+
+ // Identifiers for other resources. 0 if non existent. There is always an RLST.
+ _view.rlst = viewStream->readUint16LE();
+ if (!_view.rlst)
+ error("RLST Index missing");
+
+ _view.hint = viewStream->readUint16LE();
+ _view.init = viewStream->readUint16LE();
+ _view.exit = viewStream->readUint16LE();
+
+ delete viewStream;
+}
+
+void MohawkEngine_Myst::unloadCard() {
+ for (uint16 i = 0; i < _view.conditionalImageCount; i++)
+ delete[] _view.conditionalImages[i].values;
+
+ delete[] _view.conditionalImages;
+ _view.conditionalImageCount = 0;
+ _view.conditionalImages = NULL;
+
+ delete[] _view.soundList;
+ _view.soundList = NULL;
+ delete[] _view.soundListVolume;
+ _view.soundListVolume = NULL;
+
+ for (uint16 i = 0; i < _view.scriptResCount; i++)
+ delete[] _view.scriptResources[i].resource_list;
+
+ delete[] _view.scriptResources;
+ _view.scriptResources = NULL;
+ _view.scriptResCount = 0;
+}
+
+void MohawkEngine_Myst::runInitScript() {
+ if (!_view.init) {
+ debugC(kDebugINIT, "No INIT Present");
+ return;
+ }
+
+ debugC(kDebugINIT, "Running INIT script");
+
+ Common::SeekableReadStream *initStream = getRawData(ID_INIT, _view.init);
+
+ uint16 scriptCount = initStream->readUint16LE();
+
+ debugC(kDebugINIT, "\tOpcode Count: %d", scriptCount);
+
+ MystScriptEntry *scripts = new MystScriptEntry[scriptCount];
+
+ for (uint16 i = 0; i < scriptCount; i++) {
+ // TODO: u0 is likely variable reference for boolean to
+ // determine whether or not to execute opcode
+ uint16 u0 = initStream->readUint16LE();
+ scripts[i].opcode = initStream->readUint16LE();
+ // If variable indicates not to execute opcode, rewrite to NOP
+ //if (!_varStore->getVar(u0))
+ // scripts[i].opcode = 0xFFFF;
+ scripts[i].var = initStream->readUint16LE();
+ scripts[i].numValues = initStream->readUint16LE();
+ scripts[i].values = new uint16[scripts[i].numValues];
+
+ debugC(kDebugINIT, "\tu0: %d", u0);
+ debugC(kDebugINIT, "\tOpcode %d: %s", i, _scriptParser->getOpcodeDesc(scripts[i].opcode));
+ debugC(kDebugINIT, "\t\tUses Variable %d", scripts[i].var);
+ debugC(kDebugINIT, "\t\tHas %d Arguments:", scripts[i].numValues);
+
+ for (uint16 j = 0; j < scripts[i].numValues; j++) {
+ scripts[i].values[j] = initStream->readUint16LE();
+ debugC(kDebugINIT, "\t\tArgument %d: %d", j, scripts[i].values[j]);
+ }
+ }
+
+ delete initStream;
+
+ _scriptParser->runScript(scriptCount, scripts);
+
+ for (uint16 i = 0; i < scriptCount; i++)
+ delete[] scripts[i].values;
+ delete[] scripts;
+}
+
+void MohawkEngine_Myst::runExitScript() {
+ if (!_view.exit) {
+ debugC(kDebugEXIT, "No EXIT Present");
+ return;
+ }
+
+ debugC(kDebugEXIT, "Running EXIT script");
+
+ Common::SeekableReadStream *exitStream = getRawData(ID_EXIT, _view.exit);
+
+ uint16 scriptCount = exitStream->readUint16LE();
+
+ debugC(kDebugEXIT, "\tOpcode Count: %d", scriptCount);
+
+ MystScriptEntry *scripts = new MystScriptEntry[scriptCount];
+
+ for (uint16 i = 0; i < scriptCount; i++) {
+ // TODO: u0 is likely variable reference for boolean to
+ // to determine whether or not to execute opcode (i.e. door
+ // close noises only when door is open).
+ uint16 u0 = exitStream->readUint16LE();
+ scripts[i].opcode = exitStream->readUint16LE();
+ // If variable indicates not to execute opcode, rewrite to NOP
+ //if (!_varStore->getVar(u0))
+ // scripts[i].opcode = 0xFFFF;
+ scripts[i].var = exitStream->readUint16LE();
+ scripts[i].numValues = exitStream->readUint16LE();
+ scripts[i].values = new uint16[scripts[i].numValues];
+
+ debugC(kDebugEXIT, "\tu0: %d", u0);
+ debugC(kDebugEXIT, "\tOpcode %d: %s", i, _scriptParser->getOpcodeDesc(scripts[i].opcode));
+ debugC(kDebugEXIT, "\t\tUses Variable %d", scripts[i].var);
+ debugC(kDebugEXIT, "\t\tHas %d Arguments:", scripts[i].numValues);
+
+ for (uint16 j = 0; j < scripts[i].numValues; j++) {
+ scripts[i].values[j] = exitStream->readUint16LE();
+ debugC(kDebugEXIT, "\t\tArgument %d: %d", j, scripts[i].values[j]);
+ }
+
+ uint16 u1 = exitStream->readUint16LE();
+ if (u1 != 1)
+ warning("Myst EXIT u1 not 1");
+ }
+
+ delete exitStream;
+
+ _scriptParser->runScript(scriptCount, scripts);
+
+ for (uint16 i = 0; i < scriptCount; i++)
+ delete[] scripts[i].values;
+ delete[] scripts;
+}
+
+void MohawkEngine_Myst::loadHelp(uint16 id) {
+ // The original version did not have the help system
+ if (!(getFeatures() & GF_ME))
+ return;
+
+ // TODO: Help File contains 5 cards i.e. VIEW, RLST, etc.
+ // in addition to HELP resources.
+ // These are Ids 9930 to 9934
+ // Need to deal with loading and displaying these..
+ // Current engine structure only supports display of
+ // card from primary stack MHK
+
+ debugC(kDebugHelp, "Loading Help System Data");
+
+ Common::SeekableReadStream *helpStream = getRawData(ID_HELP, id);
+
+ uint16 count = helpStream->readUint16LE();
+ uint16 *u0 = new uint16[count];
+ Common::String helpText = Common::String::emptyString;
+
+ debugC(kDebugHelp, "\tcount: %d", count);
+
+ for (uint16 i = 0; i < count; i++) {
+ u0[i] = helpStream->readUint16LE();
+ debugC(kDebugHelp, "\tu0[%d]: %d", i, u0[i]);
+ }
+
+ // TODO: Previous values i.e. u0[0] to u0[count - 2]
+ // appear to be resource ids in the help.dat file..
+ if (u0[count - 1] != count)
+ warning("loadHelp(): last u0 value is not equal to count");
+
+ do {
+ helpText += helpStream->readByte();
+ } while (helpText.lastChar() != 0);
+ helpText.deleteLastChar();
+
+ debugC(kDebugHelp, "\thelpText: \"%s\"", helpText.c_str());
+
+ delete[] u0;
+}
+
+void MohawkEngine_Myst::loadCursorHints() {
+ for (uint16 i = 0; i < _cursorHintCount; i++)
+ delete[] _cursorHints[i].variableHint.values;
+ _cursorHintCount = 0;
+ delete[] _cursorHints;
+ _cursorHints = NULL;
+
+ if (!_view.hint) {
+ debugC(kDebugHint, "No HINT Present");
+ return;
+ }
+
+ debugC(kDebugHint, "Loading Cursor Hints:");
+
+ Common::SeekableReadStream *hintStream = getRawData(ID_HINT, _curCard);
+ _cursorHintCount = hintStream->readUint16LE();
+ debugC(kDebugHint, "Cursor Hint Count: %d", _cursorHintCount);
+ _cursorHints = new MystCursorHint[_cursorHintCount];
+
+ for (uint16 i = 0; i < _cursorHintCount; i++) {
+ debugC(kDebugHint, "Cursor Hint %d:", i);
+ _cursorHints[i].id = hintStream->readUint16LE();
+ debugC(kDebugHint, "\tId: %d", _cursorHints[i].id);
+ _cursorHints[i].cursor = hintStream->readSint16LE();
+ debugC(kDebugHint, "\tCursor: %d", _cursorHints[i].cursor);
+
+ if (_cursorHints[i].cursor == -1) {
+ debugC(kDebugHint, "\tConditional Cursor Hints:");
+ _cursorHints[i].variableHint.var = hintStream->readUint16LE();
+ debugC(kDebugHint, "\tVar: %d", _cursorHints[i].variableHint.var);
+ _cursorHints[i].variableHint.numStates = hintStream->readUint16LE();
+ debugC(kDebugHint, "\tNumber of States: %d", _cursorHints[i].variableHint.numStates);
+ _cursorHints[i].variableHint.values = new uint16[_cursorHints[i].variableHint.numStates];
+ for (uint16 j = 0; j < _cursorHints[i].variableHint.numStates; j++) {
+ _cursorHints[i].variableHint.values[j] = hintStream->readUint16LE();
+ debugC(kDebugHint, "\t\t State %d: Cursor %d", j, _cursorHints[i].variableHint.values[j]);
+ }
+ } else {
+ _cursorHints[i].variableHint.var = 0;
+ _cursorHints[i].variableHint.numStates = 0;
+ _cursorHints[i].variableHint.values = NULL;
+ }
+ }
+
+ delete hintStream;
+}
+
+void MohawkEngine_Myst::setMainCursor(uint16 cursor) {
+ _currentCursor = _mainCursor = cursor;
+ _gfx->changeCursor(_currentCursor);
+}
+
+void MohawkEngine_Myst::checkCursorHints() {
+ if (!_view.hint)
+ return;
+
+ // Check all the cursor hints to see if we're in a hotspot that contains a hint.
+ for (uint16 i = 0; i < _cursorHintCount; i++)
+ if (_cursorHints[i].id == _curResource && _resources[_cursorHints[i].id]->isEnabled()) {
+ if (_cursorHints[i].cursor == -1) {
+ uint16 var_value = _varStore->getVar(_cursorHints[i].variableHint.var);
+
+ if (var_value >= _cursorHints[i].variableHint.numStates)
+ warning("Variable %d Out of Range in variable HINT Resource %d", _cursorHints[i].variableHint.var, i);
+ else {
+ _currentCursor = _cursorHints[i].variableHint.values[var_value];
+ if (_currentCursor == 0)
+ _currentCursor = _mainCursor;
+ _gfx->changeCursor(_currentCursor);
+ }
+ } else if (_currentCursor != _cursorHints[i].cursor) {
+ if (_cursorHints[i].cursor == 0)
+ _currentCursor = _mainCursor;
+ else
+ _currentCursor = _cursorHints[i].cursor;
+
+ _gfx->changeCursor(_currentCursor);
+ }
+ return;
+ }
+
+ if (_currentCursor != _mainCursor) {
+ _currentCursor = _mainCursor;
+ _gfx->changeCursor(_currentCursor);
+ }
+}
+
+void MohawkEngine_Myst::setResourceEnabled(uint16 resourceId, bool enable) {
+ if (resourceId < _resources.size()) {
+ _resources[resourceId]->setEnabled(enable);
+ } else
+ warning("Attempt to change unknown resource enable state");
+}
+
+static MystResource *loadResource(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) {
+ uint16 type = rlstStream->readUint16LE();
+
+ debugC(kDebugResource, "\tType: %d", type);
+ debugC(kDebugResource, "\tSub_Record: %d", (parent == NULL) ? 0 : 1);
+
+ switch (type) {
+ case kMystForwardResource:
+ case kMystLeftResource:
+ case kMystRightResource:
+ case kMystDownResource:
+ case kMystUpResource:
+ case 14: // TODO: kMystBackwardResource?
+ return new MystResource(vm, rlstStream, parent);
+ case kMystActionResource:
+ return new MystResourceType5(vm, rlstStream, parent);
+ case kMystVideoResource:
+ return new MystResourceType6(vm, rlstStream, parent);
+ case kMystSwitchResource:
+ return new MystResourceType7(vm, rlstStream, parent);
+ case 8:
+ return new MystResourceType8(vm, rlstStream, parent);
+ case 10:
+ return new MystResourceType10(vm, rlstStream, parent);
+ case 11:
+ return new MystResourceType11(vm, rlstStream, parent);
+ case 12:
+ return new MystResourceType12(vm, rlstStream, parent);
+ case 13:
+ return new MystResourceType13(vm, rlstStream, parent);
+ default:
+ error ("Unknown/Unhandled MystResource type %d", type);
+ }
+}
+
+void MohawkEngine_Myst::loadResources() {
+ _resources.clear();
+
+ if (!_view.rlst) {
+ debugC(kDebugResource, "No RLST present");
+ return;
+ }
+
+ Common::SeekableReadStream *rlstStream = getRawData(ID_RLST, _view.rlst);
+ uint16 resourceCount = rlstStream->readUint16LE();
+ debugC(kDebugResource, "RLST Resource Count: %d", resourceCount);
+
+ for (uint16 i = 0; i < resourceCount; i++) {
+ debugC(kDebugResource, "Resource #%d:", i);
+ _resources.push_back(loadResource(this, rlstStream, NULL));
+ }
+ delete rlstStream;
+}
+
+MystResource::MystResource(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) {
+ _vm = vm;
+ _parent = parent;
+
+ if (parent == NULL) {
+ _flags = rlstStream->readUint16LE();
+ _rect.left = rlstStream->readSint16LE();
+ _rect.top = rlstStream->readSint16LE();
+
+ if (_rect.top == -1) {
+ warning("Invalid _rect.top of -1 found - clamping to 0");
+ _rect.top = 0;
+ }
+
+ _rect.right = rlstStream->readSint16LE();
+ _rect.bottom = rlstStream->readSint16LE();
+ _dest = rlstStream->readUint16LE();
+ } else {
+ _flags = parent->_flags;
+ _rect.left = parent->_rect.left;
+ _rect.top = parent->_rect.top;
+ _rect.right = parent->_rect.right;
+ _rect.bottom = parent->_rect.bottom;
+ _dest = parent->_dest;
+ }
+
+ debugC(kDebugResource, "\tflags: 0x%04X", _flags);
+ debugC(kDebugResource, "\tleft: %d", _rect.left);
+ debugC(kDebugResource, "\ttop: %d", _rect.top);
+ debugC(kDebugResource, "\tright: %d", _rect.right);
+ debugC(kDebugResource, "\tbottom: %d", _rect.bottom);
+ debugC(kDebugResource, "\tdest: %d", _dest);
+
+ // Default Enable based on flags...
+ if (_vm->_zipMode)
+ _enabled = (_flags & kMystZipModeEnableFlag) != 0 ||
+ (_flags & kMystHotspotEnableFlag) != 0 ||
+ (_flags & kMystSubimageEnableFlag) != 0;
+ else
+ _enabled = (_flags & kMystZipModeEnableFlag) == 0 &&
+ ((_flags & kMystHotspotEnableFlag) != 0 ||
+ (_flags & kMystSubimageEnableFlag) != 0);
+}
+
+void MystResource::handleMouseUp() {
+ if (_dest != 0)
+ _vm->changeToCard(_dest);
+ else
+ warning("Movement type resource with null destination at position (%d, %d), (%d, %d)", _rect.left, _rect.top, _rect.right, _rect.bottom);
+}
+
+MystResourceType5::MystResourceType5(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResource(vm, rlstStream, parent) {
+ debugC(kDebugResource, "\tResource Type 5 Script:");
+
+ _scriptCount = rlstStream->readUint16LE();
+
+ debugC(kDebugResource, "\tOpcode Count: %d", _scriptCount);
+
+ if (_scriptCount == 0)
+ return;
+
+ _scripts = new MystScriptEntry[_scriptCount];
+ for (uint16 i = 0; i < _scriptCount; i++) {
+ _scripts[i].opcode = rlstStream->readUint16LE();
+ _scripts[i].var = rlstStream->readUint16LE();
+ _scripts[i].numValues = rlstStream->readUint16LE();
+ _scripts[i].values = new uint16[_scripts[i].numValues];
+
+ debugC(kDebugResource, "\tOpcode %d: %s", i, _vm->_scriptParser->getOpcodeDesc(_scripts[i].opcode));
+ debugC(kDebugResource, "\t\tUses Variable %d", _scripts[i].var);
+ debugC(kDebugResource, "\t\tHas %d Arguments:", _scripts[i].numValues);
+
+ for (uint16 j = 0; j < _scripts[i].numValues; j++) {
+ _scripts[i].values[j] = rlstStream->readUint16LE();
+ debugC(kDebugResource, "\t\tArgument %d: %d", j, _scripts[i].values[j]);
+ }
+ }
+}
+
+void MystResourceType5::handleMouseUp() {
+ _vm->_scriptParser->runScript(_scriptCount, _scripts, this);
+}
+
+// In Myst/Making of Myst, the paths are hardcoded ala Windows style without extension. Convert them.
+Common::String MystResourceType6::convertMystVideoName(Common::String name) {
+ Common::String temp;
+
+ for (uint32 i = 1; i < name.size(); i++) {
+ if (name[i] == '\\')
+ temp += '/';
+ else
+ temp += name[i];
+ }
+
+ return temp + ".mov";
+}
+
+MystResourceType6::MystResourceType6(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResourceType5(vm, rlstStream, parent) {
+ char c = 0;
+
+ do {
+ c = rlstStream->readByte();
+ _videoFile += c;
+ } while (c);
+
+ rlstStream->skip(_videoFile.size() & 1);
+
+ // Trim method does not remove extra trailing nulls
+ while (_videoFile.size() != 0 && _videoFile.lastChar() == 0)
+ _videoFile.deleteLastChar();
+
+ _videoFile = convertMystVideoName(_videoFile);
+
+ // Position values require modulus 10000 to keep in sane range.
+ _left = rlstStream->readUint16LE() % 10000;
+ _top = rlstStream->readUint16LE() % 10000;
+ _loop = rlstStream->readUint16LE();
+ _u0 = rlstStream->readUint16LE();
+ _playBlocking = rlstStream->readUint16LE();
+ _playOnCardChange = rlstStream->readUint16LE();
+ _u3 = rlstStream->readUint16LE();
+
+ if (_u0 != 1)
+ warning("Type 6 _u0 != 1");
+ if (_u3 != 0)
+ warning("Type 6 _u3 != 0");
+
+ debugC(kDebugResource, "\tvideoFile: \"%s\"", _videoFile.c_str());
+ debugC(kDebugResource, "\tleft: %d", _left);
+ debugC(kDebugResource, "\ttop: %d", _top);
+ debugC(kDebugResource, "\tloop: %d", _loop);
+ debugC(kDebugResource, "\tu0: %d", _u0);
+ debugC(kDebugResource, "\tplayBlocking: %d", _playBlocking);
+ debugC(kDebugResource, "\tplayOnCardChange: %d", _playOnCardChange);
+ debugC(kDebugResource, "\tu3: %d", _u3);
+
+ _videoRunning = false;
+}
+
+void MystResourceType6::handleAnimation() {
+ // TODO: Implement Code to allow _playOnCardChange when set
+ // and trigger by Opcode 9 when clear
+
+ if (!_videoRunning) {
+ // NOTE: The left and top coordinates are often incorrect and do not make sense.
+ // We use the rect coordinates here instead.
+
+ if (_playBlocking)
+ _vm->_video->playMovie(_videoFile, _rect.left, _rect.top);
+ else
+ _vm->_video->playBackgroundMovie(_videoFile, _rect.left, _rect.top, _loop);
+
+ _videoRunning = true;
+ }
+}
+
+MystResourceType7::MystResourceType7(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResource(vm, rlstStream, parent) {
+ _var7 = rlstStream->readUint16LE();
+ _numSubResources = rlstStream->readUint16LE();
+ debugC(kDebugResource, "\tvar7: %d", _var7);
+ debugC(kDebugResource, "\tnumSubResources: %d", _numSubResources);
+
+ for (uint16 i = 0; i < _numSubResources; i++)
+ _subResources.push_back(loadResource(vm, rlstStream, this));
+}
+
+// TODO: All these functions to switch subresource are very similar.
+// Find way to share code (function pointer pass?)
+void MystResourceType7::drawDataToScreen() {
+ if (_var7 == 0xFFFF) {
+ if (_numSubResources == 1)
+ _subResources[0]->drawDataToScreen();
+ else if (_numSubResources != 0)
+ warning("Type 7 Resource with _numSubResources of %d, but no control variable", _numSubResources);
+ } else {
+ uint16 varValue = _vm->_varStore->getVar(_var7);
+
+ if (_numSubResources == 1 && varValue != 0)
+ _subResources[0]->drawDataToScreen();
+ else if (_numSubResources != 0) {
+ if (varValue < _numSubResources)
+ _subResources[varValue]->drawDataToScreen();
+ else
+ warning("Type 7 Resource Var %d: %d exceeds number of sub resources %d", _var7, varValue, _numSubResources);
+ }
+ }
+}
+
+void MystResourceType7::handleAnimation() {
+ if (_var7 == 0xFFFF) {
+ if (_numSubResources == 1)
+ _subResources[0]->handleAnimation();
+ else if (_numSubResources != 0)
+ warning("Type 7 Resource with _numSubResources of %d, but no control variable", _numSubResources);
+ } else {
+ uint16 varValue = _vm->_varStore->getVar(_var7);
+
+ if (_numSubResources == 1 && varValue != 0)
+ _subResources[0]->handleAnimation();
+ else if (_numSubResources != 0) {
+ if (varValue < _numSubResources)
+ _subResources[varValue]->handleAnimation();
+ else
+ warning("Type 7 Resource Var %d: %d exceeds number of sub resources %d", _var7, varValue, _numSubResources);
+ }
+ }
+}
+
+void MystResourceType7::handleMouseUp() {
+ if (_var7 == 0xFFFF) {
+ if (_numSubResources == 1)
+ _subResources[0]->handleMouseUp();
+ else if (_numSubResources != 0)
+ warning("Type 7 Resource with _numSubResources of %d, but no control variable", _numSubResources);
+ } else {
+ uint16 varValue = _vm->_varStore->getVar(_var7);
+
+ if (_numSubResources == 1 && varValue != 0)
+ _subResources[0]->handleMouseUp();
+ else if (_numSubResources != 0) {
+ if (varValue < _numSubResources)
+ _subResources[varValue]->handleMouseUp();
+ else
+ warning("Type 7 Resource Var %d: %d exceeds number of sub resources %d", _var7, varValue, _numSubResources);
+ }
+ }
+}
+
+void MystResourceType7::handleMouseDown() {
+ if (_var7 == 0xFFFF) {
+ if (_numSubResources == 1)
+ _subResources[0]->handleMouseDown();
+ else if (_numSubResources != 0)
+ warning("Type 7 Resource with _numSubResources of %d, but no control variable", _numSubResources);
+ } else {
+ uint16 varValue = _vm->_varStore->getVar(_var7);
+
+ if (_numSubResources == 1 && varValue != 0)
+ _subResources[0]->handleMouseDown();
+ else if (_numSubResources != 0) {
+ if (varValue < _numSubResources)
+ _subResources[varValue]->handleMouseDown();
+ else
+ warning("Type 7 Resource Var %d: %d exceeds number of sub resources %d", _var7, varValue, _numSubResources);
+ }
+ }
+}
+
+void MystResourceType7::handleMouseEnter() {
+ if (_var7 == 0xFFFF) {
+ if (_numSubResources == 1)
+ _subResources[0]->handleMouseEnter();
+ else if (_numSubResources != 0)
+ warning("Type 7 Resource with _numSubResources of %d, but no control variable", _numSubResources);
+ } else {
+ uint16 varValue = _vm->_varStore->getVar(_var7);
+
+ if (_numSubResources == 1 && varValue != 0)
+ _subResources[0]->handleMouseEnter();
+ else if (_numSubResources != 0) {
+ if (varValue < _numSubResources)
+ _subResources[varValue]->handleMouseEnter();
+ else
+ warning("Type 7 Resource Var %d: %d exceeds number of sub resources %d", _var7, varValue, _numSubResources);
+ }
+ }
+}
+
+void MystResourceType7::handleMouseLeave() {
+ if (_var7 == 0xFFFF) {
+ if (_numSubResources == 1)
+ _subResources[0]->handleMouseLeave();
+ else if (_numSubResources != 0)
+ warning("Type 7 Resource with _numSubResources of %d, but no control variable", _numSubResources);
+ } else {
+ uint16 varValue = _vm->_varStore->getVar(_var7);
+
+ if (_numSubResources == 1 && varValue != 0)
+ _subResources[0]->handleMouseLeave();
+ else if (_numSubResources != 0) {
+ if (varValue < _numSubResources)
+ _subResources[varValue]->handleMouseLeave();
+ else
+ warning("Type 7 Resource Var %d: %d exceeds number of sub resources %d", _var7, varValue, _numSubResources);
+ }
+ }
+}
+
+MystResourceType8::MystResourceType8(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResourceType7(vm, rlstStream, parent) {
+ _var8 = rlstStream->readUint16LE();
+ _numSubImages = rlstStream->readUint16LE();
+ debugC(kDebugResource, "\tvar8: %d", _var8);
+ debugC(kDebugResource, "\tnumSubImages: %d", _numSubImages);
+
+ _subImages = new MystResourceType8::SubImage[_numSubImages];
+ for (uint16 i = 0; i < _numSubImages; i++) {
+ debugC(kDebugResource, "\tSubimage %d:", i);
+
+ _subImages[i].wdib = rlstStream->readUint16LE();
+ _subImages[i].rect.left = rlstStream->readSint16LE();
+
+ if (_subImages[i].rect.left != -1) {
+ _subImages[i].rect.top = rlstStream->readSint16LE();
+ _subImages[i].rect.right = rlstStream->readSint16LE();
+ _subImages[i].rect.bottom = rlstStream->readSint16LE();
+ } else {
+ _subImages[i].rect.top = 0;
+ _subImages[i].rect.right = 0;
+ _subImages[i].rect.bottom = 0;
+ }
+
+ debugC(kDebugResource, "\twdib: %d", _subImages[i].wdib);
+ debugC(kDebugResource, "\tleft: %d", _subImages[i].rect.left);
+ debugC(kDebugResource, "\ttop: %d", _subImages[i].rect.top);
+ debugC(kDebugResource, "\tright: %d", _subImages[i].rect.right);
+ debugC(kDebugResource, "\tbottom: %d", _subImages[i].rect.bottom);
+ }
+}
+
+void MystResourceType8::drawDataToScreen() {
+ // Need to call overidden Type 7 function to ensure
+ // switch section is processed correctly.
+ MystResourceType7::drawDataToScreen();
+
+ bool drawSubImage = false;
+ int16 subImageId = 0;
+
+ if (_var8 == 0xFFFF) {
+ if (_numSubImages == 1) {
+ subImageId = 0;
+ drawSubImage = true;
+ } else if (_numSubImages != 0)
+ warning("Type 8 Resource with _numSubImages of %d, but no control variable", _numSubImages);
+ } else {
+ uint16 varValue = _vm->_varStore->getVar(_var8);
+
+ if (_numSubImages == 1 && varValue != 0) {
+ subImageId = 0;
+ drawSubImage = true;
+ } else if (_numSubImages != 0) {
+ if (varValue < _numSubImages) {
+ subImageId = varValue;
+ drawSubImage = true;
+ } else
+ warning("Type 8 Image Var %d: %d exceeds number of subImages %d", _var8, varValue, _numSubImages);
+ }
+ }
+
+ if (drawSubImage) {
+ uint16 imageToDraw = 0;
+
+ if (_subImages[subImageId].wdib == 0xFFFF) {
+ // TODO: Think the reason for problematic screen updates in some rects is that they
+ // are these -1 cases.
+ // They need to be redrawn i.e. if the Myst marker switches are changed, but I don't think
+ // the rects are valid. This does not matter in the original engine as the screen update redraws
+ // the VIEW images, followed by the RLST resource images, and -1 for the WDIB is interpreted as
+ // "Do Not Draw Image" i.e so the VIEW image is shown through.. We need to fix screen update
+ // to do this same behaviour.
+ if (_vm->_view.conditionalImageCount == 0)
+ imageToDraw = _vm->_view.mainImage;
+ else {
+ for (uint16 i = 0; i < _vm->_view.conditionalImageCount; i++)
+ if (_vm->_varStore->getVar(_vm->_view.conditionalImages[i].var) < _vm->_view.conditionalImages[i].numStates)
+ imageToDraw = _vm->_view.conditionalImages[i].values[_vm->_varStore->getVar(_vm->_view.conditionalImages[i].var)];
+ }
+ } else
+ imageToDraw = _subImages[subImageId].wdib;
+
+ if (_subImages[subImageId].rect.left == -1)
+ _vm->_gfx->copyImageSectionToScreen(imageToDraw, _rect, _rect);
+ //vm->_gfx->copyImageToScreen(imageToDraw, Common::Rect(0, 0, 544, 333));
+ // TODO: Think this is the case when the image is full screen.. need to modify graphics to add functions for returning size of image.
+ // This is not right either...
+ //else if (_rect.width() != _subImages[draw_subimage_id].rect.width() || _rect.height() != _subImages[draw_subimage_id].rect.height())
+ // HACK: Hardcode cases of this until general rule can be ascertained
+ // These cases seem to have the source rect in the wdib with an vertical i.e. top+X, bottom+X where X is a constant, but could
+ // be negative, translations, when in fact both the source and dest should be equal...
+ //else if ((vm->getCurStack() == kSeleniticStack && vm->getCurCard() == 1155 && tmp == 1) || // X=
+ // (vm->getCurStack() == kSeleniticStack && vm->getCurCard() == 1225 && tmp == 1) || // X=
+ // (vm->getCurStack() == kMystStack && vm->getCurCard() == 4247 && tmp == 0) || // X=
+ // (vm->getCurStack() == kChannelwoodStack && vm->getCurCard() == 3161 && tmp == 0)) // X=
+ // vm->_gfx->copyImageSectionToScreen(imageToDraw, _rect, _rect);
+ // // TODO: Small vertical movement remains on change. Suspect off by one error from these to real
+ // // solution.
+ else
+ _vm->_gfx->copyImageSectionToScreen(imageToDraw, _subImages[subImageId].rect, _rect);
+ }
+}
+
+uint16 MystResourceType8::getType8Var() {
+ return _var8;
+}
+
+// No MystResourceType9!
+
+MystResourceType10::MystResourceType10(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResourceType8(vm, rlstStream, parent) {
+ _kind = rlstStream->readUint16LE();
+ // NOTE: l,r,t,b differs from standard l,t,r,b order
+ _rect10.left = rlstStream->readUint16LE();
+ _rect10.right = rlstStream->readUint16LE();
+ _rect10.top = rlstStream->readUint16LE();
+ _rect10.bottom = rlstStream->readUint16LE();
+ _u0 = rlstStream->readUint16LE();
+ _u1 = rlstStream->readUint16LE();
+ _mouseDownOpcode = rlstStream->readUint16LE();
+ _mouseDragOpcode = rlstStream->readUint16LE();
+ _mouseUpOpcode = rlstStream->readUint16LE();
+
+ // TODO: Need to work out meaning of kind...
+ debugC(kDebugResource, "\tkind: %d", _kind);
+ debugC(kDebugResource, "\trect10.left: %d", _rect10.left);
+ debugC(kDebugResource, "\trect10.right: %d", _rect10.right);
+ debugC(kDebugResource, "\trect10.top: %d", _rect10.top);
+ debugC(kDebugResource, "\trect10.bottom: %d", _rect10.bottom);
+ debugC(kDebugResource, "\tu0: %d", _u0);
+ debugC(kDebugResource, "\tu1: %d", _u1);
+ debugC(kDebugResource, "\t_mouseDownOpcode: %d", _mouseDownOpcode);
+ debugC(kDebugResource, "\t_mouseDragOpcode: %d", _mouseDragOpcode);
+ debugC(kDebugResource, "\t_mouseUpOpcode: %d", _mouseUpOpcode);
+
+ // TODO: Think that u0 and u1 are unused in Type 10
+ if (_u0)
+ warning("Type 10 u0 non-zero");
+ if (_u1)
+ warning("Type 10 u1 non-zero");
+
+ // TODO: Not sure about order of Mouse Down, Mouse Drag and Mouse Up
+ // Or whether this is slightly different...
+ printf("Type 10 _mouseDownOpcode: %d\n", _mouseDownOpcode);
+ printf("Type 10 _mouseDragOpcode: %d\n", _mouseDragOpcode);
+ printf("Type 10 _mouseUpOpcode: %d\n", _mouseUpOpcode);
+
+ for (byte i = 0; i < 4; i++) {
+ debugC(kDebugResource, "\tList %d:", i);
+
+ _lists[i].listCount = rlstStream->readUint16LE();
+ debugC(kDebugResource, "\t%d values", _lists[i].listCount);
+
+ _lists[i].list = new uint16[_lists[i].listCount];
+ for (uint16 j = 0; j < _lists[i].listCount; j++) {
+ _lists[i].list[j] = rlstStream->readUint16LE();
+ debugC(kDebugResource, "\tValue %d: %d", j, _lists[i].list[j]);
+ }
+ }
+
+ warning("TODO: Card contains Type 10 Resource - Function not yet implemented");
+}
+
+void MystResourceType10::handleMouseUp() {
+ // TODO
+}
+
+MystResourceType11::MystResourceType11(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResourceType8(vm, rlstStream, parent) {
+ _kind = rlstStream->readUint16LE();
+ // NOTE: l,r,t,b differs from standard l,t,r,b order
+ _rect11.left = rlstStream->readUint16LE();
+ _rect11.right = rlstStream->readUint16LE();
+ _rect11.top = rlstStream->readUint16LE();
+ _rect11.bottom = rlstStream->readUint16LE();
+ _u0 = rlstStream->readUint16LE();
+ _u1 = rlstStream->readUint16LE();
+ _mouseDownOpcode = rlstStream->readUint16LE();
+ _mouseDragOpcode = rlstStream->readUint16LE();
+ _mouseUpOpcode = rlstStream->readUint16LE();
+
+ debugC(kDebugResource, "\tkind: %d", _kind);
+ debugC(kDebugResource, "\trect11.left: %d", _rect11.left);
+ debugC(kDebugResource, "\trect11.right: %d", _rect11.right);
+ debugC(kDebugResource, "\trect11.top: %d", _rect11.top);
+ debugC(kDebugResource, "\trect11.bottom: %d", _rect11.bottom);
+ debugC(kDebugResource, "\tu0: %d", _u0);
+ debugC(kDebugResource, "\tu1: %d", _u1);
+ debugC(kDebugResource, "\t_mouseDownOpcode: %d", _mouseDownOpcode);
+ debugC(kDebugResource, "\t_mouseDragOpcode: %d", _mouseDragOpcode);
+ debugC(kDebugResource, "\t_mouseUpOpcode: %d", _mouseUpOpcode);
+
+ // TODO: Think that u0 and u1 are unused in Type 11
+ if (_u0)
+ warning("Type 11 u0 non-zero");
+ if (_u1)
+ warning("Type 11 u1 non-zero");
+
+ // TODO: Not sure about order of Mouse Down, Mouse Drag and Mouse Up
+ // Or whether this is slightly different...
+ printf("Type 11 _mouseDownOpcode: %d\n", _mouseDownOpcode);
+ printf("Type 11 _mouseDragOpcode: %d\n", _mouseDragOpcode);
+ printf("Type 11 _mouseUpOpcode: %d\n", _mouseUpOpcode);
+
+ for (byte i = 0; i < 3; i++) {
+ debugC(kDebugResource, "\tList %d:", i);
+
+ _lists[i].listCount = rlstStream->readUint16LE();
+ debugC(kDebugResource, "\t%d values", _lists[i].listCount);
+
+ _lists[i].list = new uint16[_lists[i].listCount];
+ for (uint16 j = 0; j < _lists[i].listCount; j++) {
+ _lists[i].list[j] = rlstStream->readUint16LE();
+ debugC(kDebugResource, "\tValue %d: %d", j, _lists[i].list[j]);
+ }
+ }
+
+ warning("TODO: Card contains Type 11 Resource - Function not yet implemented");
+}
+
+void MystResourceType11::handleMouseUp() {
+ // TODO
+
+ // HACK: Myst Card 4059 (Fireplace Code Book) to usuable state
+ if (_mouseDownOpcode == 191) {
+ uint16 tmp = _vm->_varStore->getVar(0);
+ if (tmp > 0)
+ _vm->_varStore->setVar(0, tmp - 1);
+ } else if (_mouseDownOpcode == 190) {
+ _vm->_varStore->setVar(0, _vm->_varStore->getVar(0) + 1);
+ }
+}
+
+MystResourceType12::MystResourceType12(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResourceType8(vm, rlstStream, parent) {
+ _kind = rlstStream->readUint16LE();
+ // NOTE: l,r,t,b differs from standard l,t,r,b order
+ _rect11.left = rlstStream->readUint16LE();
+ _rect11.right = rlstStream->readUint16LE();
+ _rect11.top = rlstStream->readUint16LE();
+ _rect11.bottom = rlstStream->readUint16LE();
+ _state0Frame = rlstStream->readUint16LE();
+ _state1Frame = rlstStream->readUint16LE();
+ _mouseDownOpcode = rlstStream->readUint16LE();
+ _mouseDragOpcode = rlstStream->readUint16LE();
+ _mouseUpOpcode = rlstStream->readUint16LE();
+
+ debugC(kDebugResource, "\tkind: %d", _kind);
+ debugC(kDebugResource, "\trect11.left: %d", _rect11.left);
+ debugC(kDebugResource, "\trect11.right: %d", _rect11.right);
+ debugC(kDebugResource, "\trect11.top: %d", _rect11.top);
+ debugC(kDebugResource, "\trect11.bottom: %d", _rect11.bottom);
+ debugC(kDebugResource, "\t_state0Frame: %d", _state0Frame);
+ debugC(kDebugResource, "\t_state1Frame: %d", _state1Frame);
+ debugC(kDebugResource, "\t_mouseDownOpcode: %d", _mouseDownOpcode);
+ debugC(kDebugResource, "\t_mouseDragOpcode: %d", _mouseDragOpcode);
+ debugC(kDebugResource, "\t_mouseUpOpcode: %d", _mouseUpOpcode);
+
+ // TODO: Think that u0 and u1 are animation frames to be
+ // drawn for var == 0 and var == 1
+ printf("Type 12 _state0Frame: %d\n", _state0Frame);
+ printf("Type 12 _state1Frame: %d\n", _state1Frame);
+
+ // TODO: Not sure about order of Mouse Down, Mouse Drag and Mouse Up
+ // Or whether this is slightly different...
+ printf("Type 12 _mouseDownOpcode: %d\n", _mouseDownOpcode);
+ printf("Type 12 _mouseDragOpcode: %d\n", _mouseDragOpcode);
+ printf("Type 12 _mouseUpOpcode: %d\n", _mouseUpOpcode);
+
+ for (byte i = 0; i < 3; i++) {
+ debugC(kDebugResource, "\tList %d:", i);
+
+ _lists[i].listCount = rlstStream->readUint16LE();
+ debugC(kDebugResource, "\t%d values", _lists[i].listCount);
+
+ _lists[i].list = new uint16[_lists[i].listCount];
+ for (uint16 j = 0; j < _lists[i].listCount; j++) {
+ _lists[i].list[j] = rlstStream->readUint16LE();
+ debugC(kDebugResource, "\tValue %d: %d", j, _lists[i].list[j]);
+ }
+ }
+
+ warning("TODO: Card contains Type 12, Type 11 section Resource - Function not yet implemented");
+
+ _numFrames = rlstStream->readUint16LE();
+ _firstFrame = rlstStream->readUint16LE();
+ uint16 frameWidth = rlstStream->readUint16LE();
+ uint16 frameHeight = rlstStream->readUint16LE();
+ _frameRect.left = rlstStream->readUint16LE();
+ _frameRect.top = rlstStream->readUint16LE();
+
+ _frameRect.right = _frameRect.left + frameWidth;
+ _frameRect.bottom = _frameRect.top + frameHeight;
+
+ debugC(kDebugResource, "\t_numFrames: %d", _numFrames);
+ debugC(kDebugResource, "\t_firstFrame: %d", _firstFrame);
+ debugC(kDebugResource, "\tframeWidth: %d", frameWidth);
+ debugC(kDebugResource, "\tframeHeight: %d", frameHeight);
+ debugC(kDebugResource, "\t_frameRect.left: %d", _frameRect.left);
+ debugC(kDebugResource, "\t_frameRect.top: %d", _frameRect.top);
+ debugC(kDebugResource, "\t_frameRect.right: %d", _frameRect.right);
+ debugC(kDebugResource, "\t_frameRect.bottom: %d", _frameRect.bottom);
+
+ _doAnimation = false;
+}
+
+void MystResourceType12::handleAnimation() {
+ // TODO: Probably not final version. Variable/Type 11 Controlled?
+ if (_doAnimation) {
+ _vm->_gfx->copyImageToScreen(_currentFrame++, _frameRect);
+ if ((_currentFrame - _firstFrame) >= _numFrames)
+ _doAnimation = false;
+ }
+}
+
+void MystResourceType12::handleMouseUp() {
+ // HACK/TODO: Trigger Animation on Mouse Click. Probably not final version. Variable/Type 11 Controlled?
+ _currentFrame = _firstFrame;
+ _doAnimation = true;
+}
+
+MystResourceType13::MystResourceType13(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResource(vm, rlstStream, parent) {
+ _enterOpcode = rlstStream->readUint16LE();
+ _leaveOpcode = rlstStream->readUint16LE();
+
+ debugC(kDebugResource, "\t_enterOpcode: %d", _enterOpcode);
+ debugC(kDebugResource, "\t_leaveOpcode: %d", _leaveOpcode);
+}
+
+void MystResourceType13::handleMouseEnter() {
+ // Pass along the enter opcode (with no parameters) to the script parser
+ _vm->_scriptParser->runOpcode(_enterOpcode);
+}
+
+void MystResourceType13::handleMouseLeave() {
+ // Pass along the leave opcode (with no parameters) to the script parser
+ _vm->_scriptParser->runOpcode(_leaveOpcode);
+}
+
+void MystResourceType13::handleMouseUp() {
+ // Type 13 Resources do nothing on Mouse Clicks.
+ // This is required to override the inherited default
+ // i.e. MystResource::handleMouseUp
+}
+
+void MohawkEngine_Myst::runLoadDialog() {
+ runDialog(*_loadDialog);
+}
+
+Common::Error MohawkEngine_Myst::loadGameState(int slot) {
+ if (_saveLoad->loadGame(_saveLoad->generateSaveGameList()[slot])) {
+ changeToStack(kIntroStack);
+ changeToCard(5);
+ return Common::kNoError;
+ } else
+ return Common::kUnknownError;
+}
+
+Common::Error MohawkEngine_Myst::saveGameState(int slot, const char *desc) {
+ Common::StringList saveList = _saveLoad->generateSaveGameList();
+
+ if ((uint)slot < saveList.size())
+ _saveLoad->deleteSave(saveList[slot]);
+
+ return _saveLoad->saveGame(Common::String(desc)) ? Common::kNoError : Common::kUnknownError;
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/myst.h b/engines/mohawk/myst.h
new file mode 100644
index 0000000000..df2e7bb56d
--- /dev/null
+++ b/engines/mohawk/myst.h
@@ -0,0 +1,413 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MOHAWK_MYST_H
+#define MOHAWK_MYST_H
+
+#include "mohawk/console.h"
+#include "mohawk/mohawk.h"
+#include "mohawk/myst_vars.h"
+
+#include "gui/saveload.h"
+
+namespace Mohawk {
+
+class MohawkEngine_Myst;
+class VideoManager;
+class MystGraphics;
+class MystScriptParser;
+class MystConsole;
+class MystSaveLoad;
+
+// Engine Debug Flags
+enum {
+ kDebugVariable = (1 << 0),
+ kDebugSaveLoad = (1 << 1),
+ kDebugView = (1 << 2),
+ kDebugHint = (1 << 3),
+ kDebugResource = (1 << 4),
+ kDebugINIT = (1 << 5),
+ kDebugEXIT = (1 << 6),
+ kDebugScript = (1 << 7),
+ kDebugHelp = (1 << 8)
+};
+
+// Myst Stacks
+enum {
+ kChannelwoodStack = 0, // Channelwood Age
+ kCreditsStack, // Credits
+ kDemoStack, // Demo Main Menu
+ kDniStack, // D'ni
+ kIntroStack, // Intro
+ kMakingOfStack, // Making Of Myst
+ kMechanicalStack, // Mechanical Age
+ kMystStack, // Myst Island
+ kSeleniticStack, // Selenitic Age
+ kDemoSlidesStack, // Demo Slideshow
+ kDemoPreviewStack, // Demo Myst Library Preview
+ kStoneshipStack // Stoneship Age
+};
+
+const uint16 kMasterpieceOnly = 0xFFFF;
+
+// Myst Resource Types
+// TODO: Other types and such
+enum {
+ kMystForwardResource = 0,
+ kMystLeftResource = 1,
+ kMystRightResource = 2,
+ kMystDownResource = 3,
+ kMystUpResource = 4,
+ kMystActionResource = 5,
+ kMystVideoResource = 6,
+ kMystSwitchResource = 7
+};
+
+// Myst Resource Flags
+// TODO: Figure out other flags
+enum {
+ kMystSubimageEnableFlag = (1 << 0),
+ kMystHotspotEnableFlag = (1 << 1),
+ kMystUnknownFlag = (1 << 2),
+ kMystZipModeEnableFlag = (1 << 3)
+};
+
+struct MystCondition {
+ uint16 var;
+ uint16 numStates;
+ uint16 *values;
+};
+
+// View Sound Action Type
+enum {
+ kMystSoundActionConditional = -4,
+ kMystSoundActionContinue = -1,
+ kMystSoundActionChangeVolume = -2,
+ kMystSoundActionStop = -3
+ // Other positive values are PlayNewSound of that id
+};
+
+struct MystView {
+ uint16 flags;
+
+ // Image Data
+ uint16 conditionalImageCount;
+ MystCondition *conditionalImages;
+ uint16 mainImage;
+
+ // Sound Data
+ int16 sound;
+ uint16 soundVolume;
+ uint16 soundVar;
+ uint16 soundCount;
+ int16 *soundList;
+ uint16 *soundListVolume;
+
+ // Script Resources
+ uint16 scriptResCount;
+ struct ScriptResource {
+ uint16 type;
+ uint16 id; // Not used by type 3
+ // TODO: Type 3 has more. Maybe use a union?
+ uint16 var; // Used by type 3 only
+ uint16 count; // Used by type 3 only
+ uint16 u0; // Used by type 3 only
+ int16 *resource_list; // Used by type 3 only
+ } *scriptResources;
+
+ // Resource ID's
+ uint16 rlst;
+ uint16 hint;
+ uint16 init;
+ uint16 exit;
+};
+
+struct MystScriptEntry {
+ uint16 opcode;
+ uint16 var;
+ uint16 numValues;
+ uint16 *values;
+};
+
+class MystResource {
+public:
+ MystResource(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent);
+ virtual ~MystResource() {}
+
+ MystResource *_parent;
+
+ bool contains(Common::Point point) { return _rect.contains(point); }
+ virtual void drawDataToScreen() {}
+ virtual void handleAnimation() {}
+ virtual Common::Rect getRect() { return _rect; }
+ bool isEnabled() { return _enabled; }
+ void setEnabled(bool enabled) { _enabled = enabled; }
+ uint16 getDest() { return _dest; }
+ virtual uint16 getType8Var() { return 0xFFFF; }
+
+ // Mouse interface
+ virtual void handleMouseUp();
+ virtual void handleMouseDown() {}
+ virtual void handleMouseEnter() {}
+ virtual void handleMouseLeave() {}
+
+protected:
+ MohawkEngine_Myst *_vm;
+
+ uint16 _flags;
+ Common::Rect _rect;
+ uint16 _dest;
+ bool _enabled;
+};
+
+class MystResourceType5 : public MystResource {
+public:
+ MystResourceType5(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent);
+ void handleMouseUp();
+
+protected:
+ uint16 _scriptCount;
+ MystScriptEntry *_scripts;
+};
+
+class MystResourceType6 : public MystResourceType5 {
+public:
+ MystResourceType6(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent);
+ void handleAnimation();
+
+protected:
+ static Common::String convertMystVideoName(Common::String name);
+ Common::String _videoFile;
+ uint16 _left;
+ uint16 _top;
+ uint16 _loop;
+ uint16 _u0;
+ uint16 _playBlocking;
+ uint16 _playOnCardChange;
+ uint16 _u3;
+
+private:
+ bool _videoRunning;
+};
+
+struct MystResourceType7 : public MystResource {
+public:
+ MystResourceType7(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent);
+ virtual ~MystResourceType7() {}
+
+ virtual void drawDataToScreen();
+ virtual void handleAnimation();
+
+ virtual void handleMouseUp();
+ virtual void handleMouseDown();
+ virtual void handleMouseEnter();
+ virtual void handleMouseLeave();
+
+protected:
+ uint16 _var7;
+ uint16 _numSubResources;
+ Common::Array<MystResource*> _subResources;
+};
+
+class MystResourceType8 : public MystResourceType7 {
+public:
+ MystResourceType8(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent);
+ void drawDataToScreen();
+ uint16 getType8Var();
+
+protected:
+ uint16 _var8;
+ uint16 _numSubImages;
+ struct SubImage {
+ uint16 wdib;
+ Common::Rect rect;
+ } *_subImages;
+};
+
+// No MystResourceType9!
+
+class MystResourceType10 : public MystResourceType8 {
+public:
+ MystResourceType10(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent);
+ void handleMouseUp();
+
+protected:
+ uint16 _kind;
+ Common::Rect _rect10;
+ uint16 _u0;
+ uint16 _u1;
+ uint16 _mouseDownOpcode;
+ uint16 _mouseDragOpcode;
+ uint16 _mouseUpOpcode;
+ struct {
+ uint16 listCount;
+ uint16 *list;
+ } _lists[4];
+};
+
+class MystResourceType11 : public MystResourceType8 {
+public:
+ MystResourceType11(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent);
+ void handleMouseUp();
+
+protected:
+ uint16 _kind;
+ Common::Rect _rect11;
+ uint16 _u0;
+ uint16 _u1;
+ uint16 _mouseDownOpcode;
+ uint16 _mouseDragOpcode;
+ uint16 _mouseUpOpcode;
+ struct {
+ uint16 listCount;
+ uint16 *list;
+ } _lists[3];
+};
+
+class MystResourceType12 : public MystResourceType8 {
+public:
+ MystResourceType12(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent);
+ void handleAnimation();
+ void handleMouseUp();
+
+protected:
+ uint16 _kind;
+ Common::Rect _rect11;
+ uint16 _state0Frame;
+ uint16 _state1Frame;
+ uint16 _mouseDownOpcode;
+ uint16 _mouseDragOpcode;
+ uint16 _mouseUpOpcode;
+ struct {
+ uint16 listCount;
+ uint16 *list;
+ } _lists[3];
+
+ uint16 _numFrames;
+ uint16 _firstFrame;
+ Common::Rect _frameRect;
+
+private:
+ bool _doAnimation;
+ uint16 _currentFrame;
+};
+
+class MystResourceType13 : public MystResource {
+public:
+ MystResourceType13(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent);
+ void handleMouseUp();
+ void handleMouseEnter();
+ void handleMouseLeave();
+
+protected:
+ uint16 _enterOpcode;
+ uint16 _leaveOpcode;
+};
+
+struct MystCursorHint {
+ uint16 id;
+ int16 cursor;
+
+ MystCondition variableHint;
+};
+
+class MohawkEngine_Myst : public MohawkEngine {
+protected:
+ Common::Error run();
+
+public:
+ MohawkEngine_Myst(OSystem *syst, const MohawkGameDescription *gamedesc);
+ virtual ~MohawkEngine_Myst();
+
+ Common::String wrapMovieFilename(Common::String movieName, uint16 stack);
+
+ void reloadSaveList();
+ void runLoadDialog();
+ void runSaveDialog();
+
+ void changeToStack(uint16 stack);
+ void changeToCard(uint16 card);
+ uint16 getCurCard() { return _curCard; }
+ uint16 getCurStack() { return _curStack; }
+ void setMainCursor(uint16 cursor);
+
+ MystVar *_varStore;
+
+ bool _zipMode;
+ bool _transitionsEnabled;
+ bool _tweaksEnabled;
+ bool _needsUpdate;
+
+ MystView _view;
+ MystGraphics *_gfx;
+ MystSaveLoad *_saveLoad;
+ MystScriptParser *_scriptParser;
+
+ bool _showResourceRects;
+ void setResourceEnabled(uint16 resourceId, bool enable);
+
+ GUI::Debugger *getDebugger() { return _console; }
+
+ bool canLoadGameStateCurrently() { return !(getFeatures() & GF_DEMO); }
+ bool canSaveGameStateCurrently() { return !(getFeatures() & GF_DEMO); }
+ Common::Error loadGameState(int slot);
+ Common::Error saveGameState(int slot, const char *desc);
+ bool hasFeature(EngineFeature f) const;
+
+private:
+ MystConsole *_console;
+ GUI::SaveLoadChooser *_loadDialog;
+ MystOptionsDialog *_optionsDialog;
+
+ uint16 _curStack;
+ uint16 _curCard;
+
+ bool _runExitScript;
+
+ void loadCard();
+ void unloadCard();
+ void runInitScript();
+ void runExitScript();
+
+ void loadHelp(uint16 id);
+
+ Common::Array<MystResource*> _resources;
+ void loadResources();
+ void drawResourceRects();
+ void checkCurrentResource();
+ int16 _curResource;
+
+ uint16 _cursorHintCount;
+ MystCursorHint *_cursorHints;
+ void loadCursorHints();
+ void checkCursorHints();
+ Common::Point _mousePos;
+ uint16 _currentCursor;
+ uint16 _mainCursor; // Also defines the current page being held (white, blue, red, or none)
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/myst_jpeg.cpp b/engines/mohawk/myst_jpeg.cpp
new file mode 100644
index 0000000000..c6d0a00ea3
--- /dev/null
+++ b/engines/mohawk/myst_jpeg.cpp
@@ -0,0 +1,66 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/system.h"
+#include "graphics/conversion.h" // For YUV2RGB
+
+#include "mohawk/myst_jpeg.h"
+
+namespace Mohawk {
+
+MystJPEG::MystJPEG() {
+ _jpeg = new Graphics::JPEG();
+ _pixelFormat = g_system->getScreenFormat();
+
+ // We're going to have to dither if we're running in 8bpp.
+ // We'll take RGBA8888 for best color performance in this case.
+ if (_pixelFormat.bytesPerPixel == 1)
+ _pixelFormat = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0);
+}
+
+Graphics::Surface *MystJPEG::decodeImage(Common::SeekableReadStream* stream) {
+ _jpeg->read(stream);
+ Graphics::Surface *ySurface = _jpeg->getComponent(1);
+ Graphics::Surface *uSurface = _jpeg->getComponent(2);
+ Graphics::Surface *vSurface = _jpeg->getComponent(3);
+
+ Graphics::Surface *finalSurface = new Graphics::Surface();
+ finalSurface->create(ySurface->w, ySurface->h, _pixelFormat.bytesPerPixel);
+
+ for (uint16 i = 0; i < finalSurface->h; i++) {
+ for (uint16 j = 0; j < finalSurface->w; j++) {
+ byte r = 0, g = 0, b = 0;
+ Graphics::YUV2RGB(*((byte *)ySurface->getBasePtr(j, i)), *((byte *)uSurface->getBasePtr(j, i)), *((byte *)vSurface->getBasePtr(j, i)), r, g, b);
+ if (_pixelFormat.bytesPerPixel == 2)
+ *((uint16 *)finalSurface->getBasePtr(j, i)) = _pixelFormat.RGBToColor(r, g, b);
+ else
+ *((uint32 *)finalSurface->getBasePtr(j, i)) = _pixelFormat.RGBToColor(r, g, b);
+ }
+ }
+
+ return finalSurface;
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/myst_jpeg.h b/engines/mohawk/myst_jpeg.h
new file mode 100644
index 0000000000..de235ced2c
--- /dev/null
+++ b/engines/mohawk/myst_jpeg.h
@@ -0,0 +1,54 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MYST_JPEG_H
+#define MYST_JPEG_H
+
+#include "common/scummsys.h"
+#include "common/stream.h"
+
+#include "graphics/jpeg.h"
+#include "graphics/pixelformat.h"
+
+namespace Mohawk {
+
+// Myst JPEG Decoder
+// Basically a wrapper around JPEG which converts to RGB
+
+class MystJPEG {
+public:
+ MystJPEG();
+ ~MystJPEG() { delete _jpeg; }
+
+ Graphics::Surface *decodeImage(Common::SeekableReadStream *stream);
+
+private:
+ Graphics::PixelFormat _pixelFormat;
+ Graphics::JPEG *_jpeg;
+};
+
+}
+
+#endif
diff --git a/engines/mohawk/myst_pict.cpp b/engines/mohawk/myst_pict.cpp
new file mode 100644
index 0000000000..117c5ec9cc
--- /dev/null
+++ b/engines/mohawk/myst_pict.cpp
@@ -0,0 +1,247 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/system.h"
+
+#include "mohawk/myst_pict.h"
+
+namespace Mohawk {
+
+// The PICT code is based off of the QuickDraw specs:
+// http://developer.apple.com/documentation/mac/QuickDraw/QuickDraw-461.html
+
+MystPICT::MystPICT(MystJPEG *jpegDecoder) {
+ _jpegDecoder = jpegDecoder;
+ _pixelFormat = g_system->getScreenFormat();
+}
+
+Graphics::Surface *MystPICT::decodeImage(Common::SeekableReadStream *stream) {
+ // Skip initial 512 bytes (all 0's)
+ stream->seek(512, SEEK_CUR);
+
+ // Read in the first part of the header
+ /* uint16 fileSize = */ stream->readUint16BE();
+
+ _imageRect.top = stream->readUint16BE();
+ _imageRect.left = stream->readUint16BE();
+ _imageRect.bottom = stream->readUint16BE();
+ _imageRect.right = stream->readUint16BE();
+ _imageRect.debugPrint(0, "PICT Rect:");
+
+ Graphics::Surface *image = new Graphics::Surface();
+ image->create(_imageRect.width(), _imageRect.height(), _pixelFormat.bytesPerPixel);
+
+ // NOTE: This is only a subset of the full PICT format.
+ // - Only V2 Images Supported
+ // - JPEG Chunks are Supported
+ // - DirectBitsRect Chunks are Supported
+ for (uint32 opNum = 0; !stream->eos() && !stream->err() && stream->pos() < stream->size(); opNum++) {
+ uint16 opcode = stream->readUint16BE();
+ debug(2, "Found PICT opcode %04x", opcode);
+
+ if (opNum == 0 && opcode != 0x0011)
+ error ("Cannot find PICT version opcode");
+ else if (opNum == 1 && opcode != 0x0C00)
+ error ("Cannot find PICT header opcode");
+
+ if (opcode == 0x0001) { // Clip
+ // Ignore
+ uint16 clipSize = stream->readUint16BE();
+ stream->seek(clipSize - 2, SEEK_CUR);
+ } else if (opcode == 0x0007) { // PnSize
+ // Ignore
+ stream->readUint16BE();
+ stream->readUint16BE();
+ } else if (opcode == 0x0011) { // VersionOp
+ uint16 version = stream->readUint16BE();
+ if (version != 0x02FF)
+ error ("Unknown PICT version");
+ } else if (opcode == 0x001E) { // DefHilite
+ // Ignore, Contains no Data
+ } else if (opcode == 0x009A) { // DirectBitsRect
+ decodeDirectBitsRect(stream, image);
+ } else if (opcode == 0x00A1) { // LongComment
+ stream->readUint16BE();
+ uint16 dataSize = stream->readUint16BE();
+ stream->seek(dataSize, SEEK_CUR);
+ } else if (opcode == 0x00FF) { // OpEndPic
+ stream->readUint16BE();
+ break;
+ } else if (opcode == 0x0C00) { // HeaderOp
+ /* uint16 version = */ stream->readUint16BE();
+ stream->readUint16BE(); // Reserved
+ /* uint32 hRes = */ stream->readUint32BE();
+ /* uint32 vRes = */ stream->readUint32BE();
+ Common::Rect origResRect;
+ origResRect.top = stream->readUint16BE();
+ origResRect.left = stream->readUint16BE();
+ origResRect.bottom = stream->readUint16BE();
+ origResRect.right = stream->readUint16BE();
+ stream->readUint32BE(); // Reserved
+ } else if (opcode == 0x8200) { // CompressedQuickTime
+ decodeCompressedQuickTime(stream, image);
+ break;
+ } else {
+ error ("Unknown PICT opcode %04x", opcode);
+ }
+ }
+
+ return image;
+}
+
+struct DirectBitsRectData {
+ // PixMap
+ struct {
+ uint32 baseAddr;
+ uint16 rowBytes;
+ Common::Rect bounds;
+ uint16 pmVersion;
+ uint16 packType;
+ uint32 packSize;
+ uint32 hRes;
+ uint32 vRes;
+ uint16 pixelType;
+ uint16 pixelSize;
+ uint16 cmpCount;
+ uint16 cmpSize;
+ uint32 planeBytes;
+ uint32 pmTable;
+ uint32 pmReserved;
+ } pixMap;
+ Common::Rect srcRect;
+ Common::Rect dstRect;
+ uint16 mode;
+};
+
+void MystPICT::decodeDirectBitsRect(Common::SeekableReadStream *stream, Graphics::Surface *image) {
+ static const Graphics::PixelFormat directBitsFormat = Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0);
+
+ Graphics::Surface buffer;
+ buffer.create(image->w, image->h, 2);
+
+ DirectBitsRectData directBitsData;
+
+ directBitsData.pixMap.baseAddr = stream->readUint32BE();
+ directBitsData.pixMap.rowBytes = stream->readUint16BE();
+ directBitsData.pixMap.bounds.top = stream->readUint16BE();
+ directBitsData.pixMap.bounds.left = stream->readUint16BE();
+ directBitsData.pixMap.bounds.bottom = stream->readUint16BE();
+ directBitsData.pixMap.bounds.right = stream->readUint16BE();
+ directBitsData.pixMap.pmVersion = stream->readUint16BE();
+ directBitsData.pixMap.packType = stream->readUint16BE();
+ directBitsData.pixMap.packSize = stream->readUint32BE();
+ directBitsData.pixMap.hRes = stream->readUint32BE();
+ directBitsData.pixMap.vRes = stream->readUint32BE();
+ directBitsData.pixMap.pixelType = stream->readUint16BE();
+ directBitsData.pixMap.pixelSize = stream->readUint16BE();
+ directBitsData.pixMap.cmpCount = stream->readUint16BE();
+ directBitsData.pixMap.cmpSize = stream->readUint16BE();
+ directBitsData.pixMap.planeBytes = stream->readUint32BE();
+ directBitsData.pixMap.pmTable = stream->readUint32BE();
+ directBitsData.pixMap.pmReserved = stream->readUint32BE();
+ directBitsData.srcRect.top = stream->readUint16BE();
+ directBitsData.srcRect.left = stream->readUint16BE();
+ directBitsData.srcRect.bottom = stream->readUint16BE();
+ directBitsData.srcRect.right = stream->readUint16BE();
+ directBitsData.dstRect.top = stream->readUint16BE();
+ directBitsData.dstRect.left = stream->readUint16BE();
+ directBitsData.dstRect.bottom = stream->readUint16BE();
+ directBitsData.dstRect.right = stream->readUint16BE();
+ directBitsData.mode = stream->readUint16BE();
+
+ if (directBitsData.pixMap.pixelSize != 16)
+ error("Unhandled directBitsRect bitsPerPixel %d", directBitsData.pixMap.pixelSize);
+
+ // Read in amount of data per row
+ for (uint16 i = 0; i < directBitsData.pixMap.bounds.height(); i++) {
+ if (directBitsData.pixMap.packType == 1 || directBitsData.pixMap.rowBytes < 8) { // Unpacked, Pad-Byte
+ error("Pack Type = %d, Row Bytes = %d", directBitsData.pixMap.packType, directBitsData.pixMap.rowBytes);
+ // TODO
+ } else if (directBitsData.pixMap.packType == 2) { // Unpacked, No Pad-Byte
+ error("Pack Type = 2");
+ // TODO
+ } else if (directBitsData.pixMap.packType > 2) { // Packed
+ uint16 byteCount = (directBitsData.pixMap.rowBytes > 250) ? stream->readUint16BE() : stream->readByte();
+ decodeDirectBitsLine((byte *)buffer.getBasePtr(0, i), directBitsData.pixMap.rowBytes, stream->readStream(byteCount));
+ }
+ }
+
+ // Convert from 16-bit to whatever surface we need
+ for (uint16 y = 0; y < buffer.h; y++) {
+ for (uint16 x = 0; x < buffer.w; x++) {
+ byte r = 0, g = 0, b = 0;
+ uint16 color = READ_BE_UINT16(buffer.getBasePtr(x, y));
+ directBitsFormat.colorToRGB(color, r, g, b);
+ *((uint16 *)image->getBasePtr(x, y)) = _pixelFormat.RGBToColor(r, g, b);
+ }
+ }
+}
+
+void MystPICT::decodeDirectBitsLine(byte *out, uint32 length, Common::SeekableReadStream *data) {
+ uint32 dataDecoded = 0;
+ while (data->pos() < data->size() && dataDecoded < length) {
+ byte op = data->readByte();
+
+ if (op & 0x80) {
+ uint32 runSize = (op ^ 255) + 2;
+ byte value1 = data->readByte();
+ byte value2 = data->readByte();
+ for (uint32 i = 0; i < runSize; i++) {
+ *out++ = value1;
+ *out++ = value2;
+ }
+ dataDecoded += runSize * 2;
+ } else {
+ uint32 runSize = (op + 1) * 2;
+ for (uint32 i = 0; i < runSize; i++)
+ *out++ = data->readByte();
+ dataDecoded += runSize;
+ }
+ }
+
+ if (length != dataDecoded)
+ warning("Mismatched DirectBits read");
+ delete data;
+}
+
+// Compressed QuickTime details can be found here:
+// http://developer.apple.com/documentation/QuickTime/Rm/CompressDecompress/ImageComprMgr/B-Chapter/2TheImageCompression.html
+// http://developer.apple.com/documentation/QuickTime/Rm/CompressDecompress/ImageComprMgr/F-Chapter/6WorkingwiththeImage.html
+// I'm just ignoring that because Myst ME uses none of that extra stuff. The offset is always the same.
+
+void MystPICT::decodeCompressedQuickTime(Common::SeekableReadStream *stream, Graphics::Surface *image) {
+ uint32 dataSize = stream->readUint32BE();
+ uint32 startPos = stream->pos();
+
+ Graphics::Surface *jpegImage = _jpegDecoder->decodeImage(new Common::SeekableSubReadStream(stream, stream->pos() + 156, stream->pos() + dataSize));
+ stream->seek(startPos + dataSize);
+
+ image->copyFrom(*jpegImage);
+
+ jpegImage->free();
+ delete jpegImage;
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/myst_pict.h b/engines/mohawk/myst_pict.h
new file mode 100644
index 0000000000..2d763ca616
--- /dev/null
+++ b/engines/mohawk/myst_pict.h
@@ -0,0 +1,57 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MYST_PICT_H
+#define MYST_PICT_H
+
+#include "common/rect.h"
+#include "common/scummsys.h"
+#include "common/stream.h"
+#include "graphics/pixelformat.h"
+#include "graphics/surface.h"
+
+#include "mohawk/myst_jpeg.h"
+
+namespace Mohawk {
+
+class MystPICT {
+public:
+ MystPICT(MystJPEG *jpegDecoder);
+ ~MystPICT() {}
+ Graphics::Surface *decodeImage(Common::SeekableReadStream *stream);
+
+private:
+ MystJPEG *_jpegDecoder;
+ Common::Rect _imageRect;
+ Graphics::PixelFormat _pixelFormat;
+
+ void decodeDirectBitsRect(Common::SeekableReadStream *stream, Graphics::Surface *image);
+ void decodeDirectBitsLine(byte *out, uint32 length, Common::SeekableReadStream *data);
+ void decodeCompressedQuickTime(Common::SeekableReadStream *stream, Graphics::Surface *image);
+};
+
+}
+
+#endif
diff --git a/engines/mohawk/myst_saveload.cpp b/engines/mohawk/myst_saveload.cpp
new file mode 100644
index 0000000000..2f94c9e2fe
--- /dev/null
+++ b/engines/mohawk/myst_saveload.cpp
@@ -0,0 +1,676 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "mohawk/myst.h"
+#include "mohawk/myst_saveload.h"
+
+#include "common/util.h"
+
+namespace Mohawk {
+
+MystSaveLoad::MystSaveLoad(MohawkEngine_Myst *vm, Common::SaveFileManager *saveFileMan) : _vm(vm), _saveFileMan(saveFileMan) {
+ _v = new MystVariables();
+ initMystVariables(_v);
+}
+
+MystSaveLoad::~MystSaveLoad() {
+ delete _v;
+}
+
+Common::StringList MystSaveLoad::generateSaveGameList() {
+ return _saveFileMan->listSavefiles("*.mys");
+}
+
+bool MystSaveLoad::loadGame(Common::String filename) {
+ if (_vm->getFeatures() & GF_DEMO) // Don't load games in the demo
+ return false;
+
+ Common::InSaveFile *loadFile;
+ if (!(loadFile = _saveFileMan->openForLoading(filename.c_str())))
+ return false;
+ debugC(kDebugSaveLoad, "Loading game from \'%s\'", filename.c_str());
+
+ // First, let's make sure we're using a saved game file from this version of Myst
+ // By checking length of file...
+ int32 size = loadFile->size();
+ if ((size == -1)
+ || (size != 664 && (_vm->getFeatures() & GF_ME))
+ || (size != 601 && !(_vm->getFeatures() & GF_ME))) {
+ warning ("Incompatible saved game version");
+ // FIXME - Add Support to load original game saves in ME and vice versa
+ delete loadFile;
+ return false;
+ }
+
+ // Now, we'll read in the variable values.
+ // Lots of checking code here so that save files with differing formats are flagged...
+ if ((_v->game_globals[0] = loadFile->readUint16LE()) != 2)
+ warning("Unexpected value at 0x%03X - Found %u Expected %u", loadFile->pos(), _v->game_globals[0], 2);
+
+ _v->game_globals[1] = loadFile->readUint16LE();
+ _v->game_globals[2] = loadFile->readUint16LE();
+
+ if ((_v->game_globals[3] = loadFile->readUint16LE()) != 1)
+ warning("Unexpected value at 0x%03X - Found %u Expected %u", loadFile->pos(), _v->game_globals[3], 1);
+
+ _v->game_globals[4] = loadFile->readUint16LE();
+ _v->game_globals[5] = loadFile->readUint16LE();
+ _v->game_globals[6] = loadFile->readUint16LE();
+ _v->game_globals[7] = loadFile->readUint16LE();
+
+ for (byte i = 0; i < 8; i++) {
+ if (_vm->getFeatures() & GF_ME) {
+ _v->myst_vars[i] = loadFile->readUint16LE();
+
+ if (loadFile->readUint16LE() != 0)
+ warning("Non-zero value at 0x%03X", loadFile->pos());
+ } else
+ _v->myst_vars[i] = loadFile->readByte();
+
+ if (_v->myst_vars[i] > 1)
+ warning("Non-Boolean value found at 0x%03X - Found %u", loadFile->pos(), _v->myst_vars[i]);
+ }
+
+ if ((_v->myst_vars[8] = loadFile->readUint16LE()) > 1)
+ warning("Non-Boolean value found at 0x%03X - Found %u", loadFile->pos(), _v->myst_vars[8]);
+
+ if ((_v->myst_vars[9] = loadFile->readUint16LE()) > 1)
+ warning("Non-Boolean value found at 0x%03X - Found %u", loadFile->pos(), _v->myst_vars[9]);
+
+ if ((_v->myst_vars[10] = loadFile->readUint16LE()) > 25)
+ warning("Value exceeds maximum of %u found at 0x%03X - Found %u", 25, loadFile->pos(), _v->myst_vars[10]);
+
+ if ((_v->myst_vars[11] = loadFile->readUint16LE()) > 11)
+ warning("Value exceeds maximum of %u found at 0x%03X - Found %u", 11, loadFile->pos(), _v->myst_vars[11]);
+
+ _v->myst_vars[12] = loadFile->readUint16LE();
+ // TODO: Add validation of valid set for Clock Tower Hour Hand
+
+ if ((_v->myst_vars[13] = loadFile->readUint16LE()) > 1)
+ warning("Non-Boolean value found at 0x%03X - Found %u", loadFile->pos(), _v->myst_vars[13]);
+
+ if ((_v->myst_vars[14] = loadFile->readUint16LE()) > 1)
+ warning("Non-Boolean value found at 0x%03X - Found %u", loadFile->pos(), _v->myst_vars[14]);
+
+ if ((_v->myst_vars[15] = loadFile->readUint16LE()) > 2)
+ warning("Value exceeds maximum of %u found at 0x%03X - Found %u", 2, loadFile->pos(), _v->myst_vars[15]);
+
+ _v->myst_vars[16] = loadFile->readUint16LE();
+ _v->myst_vars[17] = loadFile->readUint16LE();
+
+ if ((_v->myst_vars[18] = loadFile->readUint16LE()) > 1)
+ warning("Non-Boolean value found at 0x%03X - Found %u", loadFile->pos(), _v->myst_vars[18]);
+
+ _v->myst_vars[19] = loadFile->readUint16LE();
+
+ if ((_v->myst_vars[20] = loadFile->readUint16LE()) > 1)
+ warning("Non-Boolean value found at 0x%03X - Found %u", loadFile->pos(), _v->myst_vars[20]);
+
+ if ((_v->myst_vars[21] = loadFile->readUint16LE()) != 0)
+ warning("Non-zero value at 0x%03X - Found %u", loadFile->pos(), _v->myst_vars[21]);
+
+ if ((_v->myst_vars[22] = loadFile->readUint16LE()) != 0)
+ warning("Non-zero value at 0x%03X - Found %u", loadFile->pos(), _v->myst_vars[22]);
+
+ if ((_v->myst_vars[23] = loadFile->readUint16LE()) != 0)
+ warning("Non-zero value at 0x%03X - Found %u", loadFile->pos(), _v->myst_vars[23]);
+
+ if ((_v->myst_vars[24] = loadFile->readUint16LE()) != 0)
+ warning("Non-zero value at 0x%03X - Found %u", loadFile->pos(), _v->myst_vars[24]);
+
+ if ((_v->myst_vars[25] = loadFile->readUint16LE()) > 359)
+ warning("Value exceeds maximum of %u found at 0x%03X - Found %u", 359, loadFile->pos(), _v->myst_vars[25]);
+
+ _v->myst_vars[26] = loadFile->readUint16LE();
+
+ if ((_v->myst_vars[27] = loadFile->readUint16LE()) > 1)
+ warning("Non-Boolean value found at 0x%03X - Found %u", loadFile->pos(), _v->myst_vars[27]);
+
+ _v->myst_vars[28] = loadFile->readUint16LE();
+ if (_v->myst_vars[28] < 1 && _v->myst_vars[28] > 31)
+ warning("Out of Range value found at 0x%03X - Found %u", loadFile->pos(), _v->myst_vars[28]);
+
+ if ((_v->myst_vars[29] = loadFile->readUint16LE()) > 1)
+ warning("Non-Boolean value found at 0x%03X - Found %u", loadFile->pos(), _v->myst_vars[29]);
+
+ if ((_v->myst_vars[30] = loadFile->readUint16LE()) > 11)
+ warning("Value exceeds maximum of %u found at 0x%03X - Found %u", 11, loadFile->pos(), _v->myst_vars[30]);
+
+ if ((_v->myst_vars[31] = loadFile->readUint16LE()) > (24 * 60))
+ warning("Value exceeds maximum of %u found at 0x%03X - Found %u", (24 * 60), loadFile->pos(), _v->myst_vars[31]);
+
+ _v->myst_vars[32] = loadFile->readUint16LE();
+
+ _v->myst_vars[33] = loadFile->readUint16LE();
+ if (_v->myst_vars[33] < 1 && _v->myst_vars[33] > 31)
+ warning("Out of Range value found at 0x%03X - Found %u", loadFile->pos(), _v->myst_vars[33]);
+
+ if ((_v->myst_vars[34] = loadFile->readUint16LE()) > 11)
+ warning("Value exceeds maximum of %u found at 0x%03X - Found %u", 11, loadFile->pos(), _v->myst_vars[34]);
+
+ if ((_v->myst_vars[35] = loadFile->readUint16LE()) > (24 * 60))
+ warning("Value exceeds maximum of %u found at 0x%03X - Found %u", (24 * 60), loadFile->pos(), _v->myst_vars[35]);
+
+ _v->myst_vars[36] = loadFile->readUint16LE();
+
+ if ((_v->myst_vars[37] = loadFile->readUint16LE()) > 999)
+ warning("Value exceeds maximum of %u found at 0x%03X - Found %u", 999, loadFile->pos(), _v->myst_vars[37]);
+
+ if ((_v->myst_vars[38] = loadFile->readUint16LE()) > 12)
+ warning("Value exceeds maximum of %u found at 0x%03X - Found %u", 12, loadFile->pos(), _v->myst_vars[38]);
+
+ _v->myst_vars[39] = loadFile->readUint16LE();
+ _v->myst_vars[40] = loadFile->readUint16LE();
+
+ _v->myst_vars[41] = loadFile->readUint16LE();
+ _v->myst_vars[42] = loadFile->readUint16LE();
+ _v->myst_vars[43] = loadFile->readUint16LE();
+ _v->myst_vars[44] = loadFile->readUint16LE();
+ _v->myst_vars[45] = loadFile->readUint16LE();
+
+ _v->myst_vars[46] = loadFile->readUint16LE();
+ _v->myst_vars[47] = loadFile->readUint16LE();
+ _v->myst_vars[48] = loadFile->readUint16LE();
+ _v->myst_vars[49] = loadFile->readUint16LE();
+
+ for (byte i = 0; i < 4; i++) {
+ if (_vm->getFeatures() & GF_ME) {
+ _v->channelwood_vars[i] = loadFile->readUint16LE();
+
+ if (loadFile->readUint16LE() != 0)
+ warning("Non-zero value at 0x%03X", loadFile->pos());
+ } else
+ _v->channelwood_vars[i] = loadFile->readByte();
+
+ if (_v->channelwood_vars[i] > 1)
+ warning("Non-Boolean value found at 0x%03X - Found %u", loadFile->pos(), _v->channelwood_vars[i]);
+ }
+
+ _v->channelwood_vars[4] = loadFile->readUint16LE();
+
+ if ((_v->channelwood_vars[5] = loadFile->readUint16LE()) > 3)
+ warning("Value exceeds maximum of %u found at 0x%03X - Found %u", 3, loadFile->pos(), _v->channelwood_vars[5]);
+
+ if ((_v->channelwood_vars[6] = loadFile->readUint16LE()) > 1)
+ warning("Non-Boolean value found at 0x%03X - Found %u", loadFile->pos(), _v->channelwood_vars[6]);
+
+ if (_vm->getFeatures() & GF_ME) {
+ if (loadFile->readUint16LE() != 0)
+ warning("Non-zero value at 0x%03X", loadFile->pos());
+
+ if (loadFile->readUint16LE() != 0)
+ warning("Non-zero value at 0x%03X", loadFile->pos());
+ } else if (loadFile->readByte() != 0)
+ warning("Non-zero value at 0x%03X", loadFile->pos());
+
+ for (byte i = 0; i < 3; i++)
+ if ((_v->mech_vars[i] = loadFile->readUint16LE()) > 1)
+ warning("Non-Boolean value found at 0x%03X - Found %u", loadFile->pos(), _v->mech_vars[i]);
+
+ _v->mech_vars[3] = loadFile->readUint16LE();
+ for (byte i = 4; i < 8; i++)
+ if ((_v->mech_vars[i] = loadFile->readUint16LE()) > 9)
+ warning("Value exceeds maximum of %u found at 0x%03X - Found %u", 9, loadFile->pos(), _v->mech_vars[i]);
+
+ for (byte i = 0; i < 7; i++) {
+ if (_vm->getFeatures() & GF_ME) {
+ _v->selenitic_vars[i] = loadFile->readUint16LE();
+
+ if (loadFile->readUint16LE() != 0)
+ warning("Non-zero value at 0x%03X", loadFile->pos());
+ } else
+ _v->selenitic_vars[i] = loadFile->readByte();
+
+ if (_v->selenitic_vars[i] > 1)
+ warning("Non-Boolean value found at 0x%03X - Found %u", loadFile->pos(), _v->selenitic_vars[i]);
+ }
+
+ for(byte i = 7; i < 18; i++)
+ _v->selenitic_vars[i] = loadFile->readUint16LE();
+
+ for (byte i = 0; i < 3; i++) {
+ if (_vm->getFeatures() & GF_ME) {
+ _v->stoneship_vars[i] = loadFile->readUint16LE();
+ } else
+ _v->stoneship_vars[i] = loadFile->readByte();
+
+ if (_v->stoneship_vars[i] > 1)
+ warning("Non-Boolean value found at 0x%03X - Found %u", loadFile->pos(), _v->selenitic_vars[i]);
+ }
+ for (byte i = 3; i < 14; i++)
+ _v->stoneship_vars[i] = loadFile->readUint16LE();
+
+ for (byte i = 0; i < 1; i++)
+ _v->dunny_vars[i] = loadFile->readUint16LE();
+
+ // Reading unknown region...
+ // When Zero Value regions are included, these are 5 blocks of
+ // 41 uint16 values.
+
+ for (byte i = 0; i < 31; i++)
+ _v->unknown_myst[i] = loadFile->readUint16LE();
+
+ for (byte i = 0; i < 10; i++) {
+ if (loadFile->readUint16LE() != 0)
+ warning("Non-zero value at 0x%03X", loadFile->pos());
+ }
+
+ for (byte i = 0; i < 37; i++)
+ _v->unknown_channelwood[i] = loadFile->readUint16LE();
+
+ for (byte i = 0; i < 4; i++) {
+ if (loadFile->readUint16LE() != 0)
+ warning("Non-zero value at 0x%03X", loadFile->pos());
+ }
+
+ for (byte i = 0; i < 18; i++)
+ _v->unknown_mech[i] = loadFile->readUint16LE();
+
+ for (byte i = 0; i < 23; i++) {
+ if (loadFile->readUint16LE() != 0)
+ warning("Non-zero value at 0x%03X", loadFile->pos());
+ }
+
+ for (byte i = 0; i < 30; i++)
+ _v->unknown_selenitic[i] = loadFile->readUint16LE();
+
+ for (byte i = 0; i < 11; i++) {
+ if (loadFile->readUint16LE() != 0)
+ warning("Non-zero value at 0x%03X", loadFile->pos());
+ }
+
+ for (byte i = 0; i < 22; i++)
+ _v->unknown_stoneship[i] = loadFile->readUint16LE();
+
+ for (byte i = 0; i < 19; i++) {
+ if (loadFile->readUint16LE() != 0)
+ warning("Non-zero value at 0x%03X", loadFile->pos());
+ }
+
+ if (_vm->getFeatures() & GF_ME) {
+ if (loadFile->pos() != 664)
+ warning("Unexpected File Position 0x%03X At End of Load", loadFile->pos());
+ } else {
+ if (loadFile->pos() != 601)
+ warning("Unexpected File Position 0x%03X At End of Load", loadFile->pos());
+ }
+
+ delete loadFile;
+
+ debug_printMystVariables(_v);
+
+ return true;
+}
+
+bool MystSaveLoad::saveGame(Common::String filename) {
+ // Make sure we have the right extension
+ if (!filename.hasSuffix(".mys") && !filename.hasSuffix(".MYS"))
+ filename += ".mys";
+
+ Common::OutSaveFile *saveFile;
+ if (!(saveFile = _saveFileMan->openForSaving(filename.c_str())))
+ return false;
+ debugC(kDebugSaveLoad, "Saving game to \'%s\'", filename.c_str());
+
+ debug_printMystVariables(_v);
+
+ // Performs no validation of variable values - Assumes they are valid.
+ for (byte i = 0; i < 8; i++)
+ saveFile->writeUint16LE(_v->game_globals[i]);
+
+ for (byte i = 0; i < 8; i++) {
+ if (_vm->getFeatures() & GF_ME) {
+ saveFile->writeUint16LE(_v->myst_vars[i]);
+ saveFile->writeUint16LE(0);
+ } else
+ saveFile->writeByte(_v->myst_vars[i]);
+ }
+
+ for (byte i = 8; i < 50; i++)
+ saveFile->writeUint16LE(_v->myst_vars[i]);
+
+ for (byte i = 0; i < 4; i++) {
+ if (_vm->getFeatures() & GF_ME) {
+ saveFile->writeUint16LE(_v->channelwood_vars[i]);
+ saveFile->writeUint16LE(0);
+ } else
+ saveFile->writeByte(_v->channelwood_vars[i]);
+ }
+
+ for (byte i = 4; i < 7; i++)
+ saveFile->writeUint16LE(_v->channelwood_vars[i]);
+
+ if (_vm->getFeatures() & GF_ME) {
+ saveFile->writeUint16LE(0);
+ saveFile->writeUint16LE(0);
+ } else
+ saveFile->writeByte(0);
+
+ for (byte i = 0; i < 8; i++)
+ saveFile->writeUint16LE(_v->mech_vars[i]);
+
+ for (byte i = 0; i < 7; i++) {
+ if (_vm->getFeatures() & GF_ME) {
+ saveFile->writeUint16LE(_v->selenitic_vars[i]);
+ saveFile->writeUint16LE(0);
+ } else
+ saveFile->writeByte(_v->selenitic_vars[i]);
+ }
+
+ for(byte i = 7; i < 18; i++)
+ saveFile->writeUint16LE(_v->selenitic_vars[i]);
+
+ for (byte i = 0; i < 3; i++) {
+ if (_vm->getFeatures() & GF_ME) {
+ saveFile->writeUint16LE(_v->stoneship_vars[i]);
+ } else
+ saveFile->writeByte(_v->stoneship_vars[i]);
+ }
+ for (byte i = 3; i < 14; i++)
+ saveFile->writeUint16LE(_v->stoneship_vars[i]);
+
+ for (byte i = 0; i < 1; i++)
+ saveFile->writeUint16LE(_v->dunny_vars[i]);
+
+ for (byte i = 0; i < 31; i++)
+ saveFile->writeUint16LE(_v->unknown_myst[i]);
+
+ for (byte i = 0; i < 10; i++)
+ saveFile->writeUint16LE(0);
+
+ for (byte i = 0; i < 37; i++)
+ saveFile->writeUint16LE(_v->unknown_channelwood[i]);
+
+ for (byte i = 0; i < 4; i++)
+ saveFile->writeUint16LE(0);
+
+ for (byte i = 0; i < 18; i++)
+ saveFile->writeUint16LE(_v->unknown_mech[i]);
+
+ for (byte i = 0; i < 23; i++)
+ saveFile->writeUint16LE(0);
+
+ for (byte i = 0; i < 30; i++)
+ saveFile->writeUint16LE(_v->unknown_selenitic[i]);
+
+ for (byte i = 0; i < 11; i++)
+ saveFile->writeUint16LE(0);
+
+ for (byte i = 0; i < 22; i++)
+ saveFile->writeUint16LE(_v->unknown_stoneship[i]);
+
+ for (byte i = 0; i < 19; i++)
+ saveFile->writeUint16LE(0);
+
+ saveFile->finalize();
+
+ delete saveFile;
+
+ return true;
+}
+
+void MystSaveLoad::deleteSave(Common::String saveName) {
+ debugC(kDebugSaveLoad, "Deleting save file \'%s\'", saveName.c_str());
+ _saveFileMan->removeSavefile(saveName.c_str());
+}
+
+void MystSaveLoad::initMystVariables(MystVariables *_tv) {
+ uint8 i;
+
+ // Most of the variables are zero at game start.
+ for (i = 0; i < ARRAYSIZE(_tv->game_globals); i++)
+ _tv->game_globals[i] = 0;
+ for (i = 0; i < ARRAYSIZE(_tv->myst_vars); i++)
+ _tv->myst_vars[i] = 0;
+ for (i = 0; i < ARRAYSIZE(_tv->channelwood_vars); i++)
+ _tv->channelwood_vars[i] = 0;
+ for (i = 0; i < ARRAYSIZE(_tv->mech_vars); i++)
+ _tv->mech_vars[i] = 0;
+ for (i = 0; i < ARRAYSIZE(_tv->selenitic_vars); i++)
+ _tv->selenitic_vars[i] = 0;
+ for (i = 0; i < ARRAYSIZE(_tv->stoneship_vars); i++)
+ _tv->stoneship_vars[i] = 0;
+ for (i = 0; i < ARRAYSIZE(_tv->dunny_vars); i++)
+ _tv->dunny_vars[i] = 0;
+ for (i = 0; i < ARRAYSIZE(_tv->unknown_myst); i++)
+ _tv->unknown_myst[i] = 0;
+ for (i = 0; i < ARRAYSIZE(_tv->unknown_channelwood); i++)
+ _tv->unknown_channelwood[i] = 0;
+ for (i = 0; i < ARRAYSIZE(_tv->unknown_mech); i++)
+ _tv->unknown_mech[i] = 0;
+ for (i = 0; i < ARRAYSIZE(_tv->unknown_selenitic); i++)
+ _tv->unknown_selenitic[i] = 0;
+ for (i = 0; i < ARRAYSIZE(_tv->unknown_stoneship); i++)
+ _tv->unknown_stoneship[i] = 0;
+
+ // TODO: Not all these may be needed as some of the unknown opcodes
+ // called by init scripts may set these up as per the others..
+
+ // Unknown - Fixed at 2
+ _tv->game_globals[0] = 2;
+ // Current Age / Stack - Start in Myst
+ _tv->game_globals[1] = 2;
+ // Unknown - Fixed at 1
+ _tv->game_globals[3] = 1;
+
+ // Library Bookcase Door - Default to Up
+ _tv->myst_vars[18] = 1;
+ // Dock Imager Numeric Selection - Default to 67
+ _tv->myst_vars[19] = 67;
+ // Dock Imager Active - Default to Active
+ _tv->myst_vars[20] = 1;
+ // Stellar Observatory Lights - Default to On
+ _tv->myst_vars[29] = 1;
+
+ // Lighthouse Trapdoor State - Default to Locked
+ _tv->stoneship_vars[4] = 2;
+ // Lighthouse Chest Water State - Default to Full
+ _tv->stoneship_vars[5] = 1;
+}
+
+static const char *game_globals_names[] = {
+ "Unknown - Fixed at 2",
+ "Current Age / Stack",
+ "Page Being Held",
+ "Unknown - Fixed at 1",
+ "Slide Transistions",
+ "Zip Mode",
+ "Red Pages in Book",
+ "Blue Pages in Book"
+};
+
+static const char *myst_vars_names[] = {
+ "Marker Switch Near Cabin",
+ "Marker Switch Near Clock Tower",
+ "Marker Switch on Dock",
+ "Marker Switch Near Ship Pool",
+ "Marker Switch Near Cogs",
+ "Marker Switch Near Generator Room",
+ "Marker Switch Near Stellar Observatory",
+ "Marker Switch Near Rocket Ship",
+ "Fireplace, Opened Green Book Before",
+ "Ship State",
+ "Cabin Gas Valve Position",
+ "Clock Tower Hour Hand Position",
+ "Clock Tower Minute Hand Position",
+ "Clock Tower Puzzle Solved / Cogs Open",
+ "Clock Tower Gear Bridge",
+ "Generator Breaker State",
+ "Generator Button State",
+ "Generator Voltage State",
+ "Library Bookcase Door",
+ "Dock Imager Numeric Selection",
+ "Dock Imager Active",
+ "Unknown #1 - Fixed at 0",
+ "Unknown #2 - Fixed at 0",
+ "Unknown #3 - Fixed at 0",
+ "Unknown #4 - Fixed at 0",
+ "Tower Rotation Angle",
+ "Boxes For Ship Float Puzzle",
+ "Tree Boiler Pilot Light Lit",
+ "Stellar Observatory Viewer, Control Setting Day",
+ "Stellar Observatory Lights",
+ "Stellar Observatory Viewer, Control Setting Month",
+ "Stellar Observatory Viewer, Control Setting Time",
+ "Stellar Observatory Viewer, Control Setting Year",
+ "Stellar Observatory Viewer, Target Day",
+ "Stellar Observatory Viewer, Target Month",
+ "Stellar Observatory Viewer, Target Time",
+ "Stellar Observatory Viewer, Target Year",
+ "Cabin Safe Combination",
+ "Channelwood Tree Position",
+ "Checksum? #1",
+ "Checksum? #2",
+ "Rocketship Music Puzzle Slider #1 Position",
+ "Rocketship Music Puzzle Slider #2 Position",
+ "Rocketship Music Puzzle Slider #3 Position",
+ "Rocketship Music Puzzle Slider #4 Position",
+ "Rocketship Music Puzzle Slider #5 Position",
+ "Unknown #5",
+ "Unknown #6",
+ "Unknown #7",
+ "Unknown #8"
+};
+
+static const char *channelwood_vars_names[] = {
+ "Water Pump Bridge State",
+ "Lower Walkway to Upper Walkway Elevator State",
+ "Lower Walkway to Upper Walkway Spiral Stair Lower Door State",
+ "Extendable Pipe State",
+ "Water Valve States",
+ "Achenar's Holoprojector Selection",
+ "Lower Walkway to Upper Walkway Spiral Stair Upper Door State"
+};
+
+static const char *mech_vars_names[] = {
+ "Achenar's Room Secret Panel State",
+ "Sirrus' Room Secret Panel State",
+ "Fortress Staircase State",
+ "Fortress Elevator Rotation",
+ "Code Lock Shape #1 (Left)",
+ "Code Lock Shape #2",
+ "Code Lock Shape #3",
+ "Code Lock Shape #4 (Right)"
+};
+
+static const char *selenitic_vars_names[] = {
+ "Sound Pickup At Water Pool",
+ "Sound Pickup At Volcanic Crack",
+ "Sound Pickup At Clock",
+ "Sound Pickup At Crystal Rocks",
+ "Sound Pickup At Windy Tunnel",
+ "Sound Receiver Doors",
+ "Windy Tunnel Lights",
+ "Sound Receiver Current Input",
+ "Sound Receiver Input #0 (Water Pool) Angle Value",
+ "Sound Receiver Input #1 (Volcanic Crack) Angle Value",
+ "Sound Receiver Input #2 (Clock) Angle Value",
+ "Sound Receiver Input #3 (Crystal Rocks) Angle Value",
+ "Sound Receiver Input #4 (Windy Tunnel) Angle Value",
+ "Sound Lock Slider #1 (Left) Position",
+ "Sound Lock Slider #2 Position",
+ "Sound Lock Slider #3 Position",
+ "Sound Lock Slider #4 Position",
+ "Sound Lock Slider #5 (Right) Position"
+};
+
+static const char *stoneship_vars_names[] = {
+ "Light State",
+ "Unknown #1",
+ "Unknown #2",
+ "Water Pump State",
+ "Lighthouse Trapdoor State",
+ "Lighthouse Chest Water State",
+ "Lighthouse Chest Valve State",
+ "Lighthouse Chest Open State",
+ "Lighthouse Trapdoor Key State",
+ "Lighthouse Generator Power Level(?)",
+ "Lighthouse Generator Power...?",
+ "Lighthouse Generator Power Good",
+ "Lighthouse Generator Power #1 ?",
+ "Lighthouse Generator Power #2?"
+};
+
+static const char *dunny_vars_names[] = {
+ "Outcome State"
+};
+
+void MystSaveLoad::debug_printMystVariables(MystVariables *_tv) {
+ uint8 i;
+
+ debugC(kDebugSaveLoad, "Printing Myst Variable State:");
+
+ debugC(kDebugSaveLoad, " Game Globals:");
+ for (i = 0; i < ARRAYSIZE(_tv->game_globals); i++)
+ debugC(kDebugSaveLoad, " %s: %u", game_globals_names[i], _tv->game_globals[i]);
+
+ debugC(kDebugSaveLoad, " Myst Variables:");
+ for (i = 0; i < ARRAYSIZE(_tv->myst_vars); i++)
+ debugC(kDebugSaveLoad, " %s: %u", myst_vars_names[i], _tv->myst_vars[i]);
+
+ debugC(kDebugSaveLoad, " Channelwood Variables:");
+ for (i = 0; i < ARRAYSIZE(_tv->channelwood_vars); i++)
+ debugC(kDebugSaveLoad, " %s: %u", channelwood_vars_names[i], _tv->channelwood_vars[i]);
+
+ debugC(kDebugSaveLoad, " Mech Variables:");
+ for (i = 0; i < ARRAYSIZE(_tv->mech_vars); i++)
+ debugC(kDebugSaveLoad, " %s: %u", mech_vars_names[i], _tv->mech_vars[i]);
+
+ debugC(kDebugSaveLoad, " Selenitic Variables:");
+ for (i = 0; i < ARRAYSIZE(_tv->selenitic_vars); i++)
+ debugC(kDebugSaveLoad, " %s: %u", selenitic_vars_names[i], _tv->selenitic_vars[i]);
+
+ debugC(kDebugSaveLoad, " Stoneship Variables:");
+ for (i = 0; i < ARRAYSIZE(_tv->stoneship_vars); i++)
+ debugC(kDebugSaveLoad, " %s: %u", stoneship_vars_names[i], _tv->stoneship_vars[i]);
+
+ debugC(kDebugSaveLoad, " Dunny Variables:");
+ for (i = 0; i < ARRAYSIZE(_tv->dunny_vars); i++)
+ debugC(kDebugSaveLoad, " %s: %u", dunny_vars_names[i], _tv->dunny_vars[i]);
+
+ debugC(kDebugSaveLoad, " Other Variables:");
+
+ debugC(kDebugSaveLoad, " Unknown Myst:");
+ for (i = 0; i < ARRAYSIZE(_tv->unknown_myst); i++)
+ debugC(kDebugSaveLoad, " %u: 0x%04X - %u", i, _tv->unknown_myst[i], _tv->unknown_myst[i]);
+
+ debugC(kDebugSaveLoad, " Unknown Channelwood:");
+ for (i = 0; i < ARRAYSIZE(_tv->unknown_channelwood); i++)
+ debugC(kDebugSaveLoad, " %u: 0x%04X - %u", i, _tv->unknown_channelwood[i], _tv->unknown_channelwood[i]);
+
+ debugC(kDebugSaveLoad, " Unknown Mech:");
+ for (i = 0; i < ARRAYSIZE(_tv->unknown_mech); i++)
+ debugC(kDebugSaveLoad, " %u: 0x%04X - %u", i, _tv->unknown_mech[i], _tv->unknown_mech[i]);
+
+ debugC(kDebugSaveLoad, " Unknown Selenitic:");
+ for (i = 0; i < ARRAYSIZE(_tv->unknown_selenitic); i++)
+ debugC(kDebugSaveLoad, " %u: 0x%04X - %u", i, _tv->unknown_selenitic[i], _tv->unknown_selenitic[i]);
+
+ debugC(kDebugSaveLoad, " Unknown Stoneship:");
+ for (i = 0; i < ARRAYSIZE(_tv->unknown_stoneship); i++)
+ debugC(kDebugSaveLoad, " %u: 0x%04X - %u", i, _tv->unknown_stoneship[i], _tv->unknown_stoneship[i]);
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/myst_saveload.h b/engines/mohawk/myst_saveload.h
new file mode 100644
index 0000000000..0653be0a00
--- /dev/null
+++ b/engines/mohawk/myst_saveload.h
@@ -0,0 +1,212 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MYST_SAVELOAD_H
+#define MYST_SAVELOAD_H
+
+#include "common/savefile.h"
+#include "common/file.h"
+#include "common/str.h"
+
+namespace Mohawk {
+
+// These are left as uint16 currently, rather than
+// being changed to bool etc. to save memory.
+// This is because the exact structure
+// is subject to change and the code to implement
+// opcodes to access them is simpler this way..
+struct MystVariables {
+ MystVariables() { memset(this, 0, sizeof(MystVariables)); }
+
+ /* 8 Game Global Variables :
+ 0 = Unknown - Fixed at 2
+ 1 = Current Age / Stack
+ 2 = Page Being Held
+ 3 = Unknown - Fixed at 1
+ 4 = Slide Transistions
+ 5 = Zip Mode
+ 6 = Red Pages in Book
+ 7 = Blue Pages in Book
+ */
+ uint16 game_globals[8];
+
+ /* 50 Myst Specific Variables :
+ 0 = Marker Switch Near Cabin
+ 1 = Marker Switch Near Clock Tower
+ 2 = Marker Switch on Dock
+ 3 = Marker Switch Near Ship Pool
+ 4 = Marker Switch Near Cogs
+ 5 = Marker Switch Near Generator Room
+ 6 = Marker Switch Near Stellar Observatory
+ 7 = Marker Switch Near Rocket Ship
+ 8 = Fireplace, Opened Green Book Before
+ 9 = Ship State
+ 10 = Cabin Gas Valve Position
+ 11 = Clock Tower Hour Hand Position
+ 12 = Clock Tower Minute Hand Position
+ 13 = Clock Tower Puzzle Solved / Cogs Open
+ 14 = Clock Tower Gear Bridge
+ 15 = Generator Breaker State
+ 16 = Generator Button State
+ 17 = Generator Voltage State
+ 18 = Library Bookcase Door
+ 19 = Dock Imager Numeric Selection
+ 20 = Dock Imager Active
+ 21 = Unknown #1 - Fixed at 0
+ 22 = Unknown #2 - Fixed at 0
+ 23 = Unknown #3 - Fixed at 0
+ 24 = Unknown #4 - Fixed at 0
+ 25 = Tower Rotation Angle
+ 26 = Boxes For Ship Float Puzzle
+ 27 = Tree Boiler Pilot Light Lit
+ 28 = Stellar Observatory Viewer, Control Setting Day
+ 29 = Stellar Observatory Lights
+ 30 = Stellar Observatory Viewer, Control Setting Month
+ 31 = Stellar Observatory Viewer, Control Setting Time
+ 32 = Stellar Observatory Viewer, Control Setting Year
+ 33 = Stellar Observatory Viewer, Target Day
+ 34 = Stellar Observatory Viewer, Target Month
+ 35 = Stellar Observatory Viewer, Target Time
+ 36 = Stellar Observatory Viewer, Target Year
+ 37 = Cabin Safe Combination
+ 38 = Channelwood Tree Position
+ 39 = Checksum? #1
+ 40 = Checksum? #2
+ 41 = Rocketship Music Puzzle Slider #1 Position
+ 42 = Rocketship Music Puzzle Slider #2 Position
+ 43 = Rocketship Music Puzzle Slider #3 Position
+ 44 = Rocketship Music Puzzle Slider #4 Position
+ 45 = Rocketship Music Puzzle Slider #5 Position
+ 46 = Unknown #5
+ 47 = Unknown #6
+ 48 = Unknown #7
+ 49 = Unknown #8
+ */
+ uint16 myst_vars[50];
+
+ /* 7 Channelwood Specific Variables :
+ 0 = Water Pump Bridge State
+ 1 = Lower Walkway to Upper Walkway Elevator State
+ 2 = Lower Walkway to Upper Walkway Spiral Stair Lower Door State
+ 3 = Extendable Pipe State
+ 4 = Water Valve States
+ 5 = Achenar's Holoprojector Selection
+ 6 = Lower Walkway to Upper Walkway Spiral Stair Upper Door State
+ */
+ uint16 channelwood_vars[7];
+
+ /* 8 Mech Specific Variables :
+ 0 = Achenar's Room Secret Panel State
+ 1 = Sirrus' Room Secret Panel State
+ 2 = Fortress Staircase State
+ 3 = Fortress Elevator Rotation
+ 4 = Code Lock Shape #1 (Left)
+ 5 = Code Lock Shape #2
+ 6 = Code Lock Shape #3
+ 7 = Code Lock Shape #4 (Right)
+ */
+ uint16 mech_vars[8];
+
+ /* 18 Selenitic Specific Variables :
+ 0 = Sound Pickup At Water Pool
+ 1 = Sound Pickup At Volcanic Crack
+ 2 = Sound Pickup At Clock
+ 3 = Sound Pickup At Crystal Rocks
+ 4 = Sound Pickup At Windy Tunnel
+ 5 = Sound Receiver Doors
+ 6 = Windy Tunnel Lights
+ 7 = Sound Receiver Current Input
+ 8 = Sound Receiver Input #0 (Water Pool) Angle Value
+ 9 = Sound Receiver Input #1 (Volcanic Crack) Angle Value
+ 10 = Sound Receiver Input #2 (Clock) Angle Value
+ 11 = Sound Receiver Input #3 (Crystal Rocks) Angle Value
+ 12 = Sound Receiver Input #4 (Windy Tunnel) Angle Value
+ 13 = Sound Lock Slider #1 (Left) Position
+ 14 = Sound Lock Slider #2 Position
+ 15 = Sound Lock Slider #3 Position
+ 16 = Sound Lock Slider #4 Position
+ 17 = Sound Lock Slider #5 (Right) Position
+ */
+ uint16 selenitic_vars[18];
+
+ /* 14 Stoneship Specific Variables :
+ 0 = Light State
+ 1 = Unknown #1
+ 2 = Unknown #2
+ 3 = Water Pump State
+ 4 = Lighthouse Trapdoor State
+ 5 = Lighthouse Chest Water State
+ 6 = Lighthouse Chest Valve State
+ 7 = Lighthouse Chest Open State
+ 8 = Lighthouse Trapdoor Key State
+ 9 = Lighthouse Generator Power Level(?)
+ 10 = Lighthouse Generator Power...?
+ 11 = Lighthouse Generator Power Good
+ 12 = Lighthouse Generator Power #1 ?
+ 13 = Lighthouse Generator Power #2?
+ */
+ uint16 stoneship_vars[14];
+
+ /* 1 Dunny Specific Variable :
+ 0 = Outcome State
+ */
+ uint16 dunny_vars[1];
+
+ // The values in these regions seem to be lists of resource IDs
+ // which correspond to VIEW resources i.e. cards
+ uint16 unknown_myst[31];
+
+ uint16 unknown_channelwood[37];
+
+ uint16 unknown_mech[18];
+
+ uint16 unknown_selenitic[30];
+
+ uint16 unknown_stoneship[22];
+};
+
+class MohawkEngine_Myst;
+
+class MystSaveLoad {
+public:
+ MystSaveLoad(MohawkEngine_Myst*, Common::SaveFileManager*);
+ ~MystSaveLoad();
+
+ Common::StringList generateSaveGameList();
+ bool loadGame(Common::String);
+ bool saveGame(Common::String);
+ void deleteSave(Common::String);
+
+ void initMystVariables(MystVariables *_tv);
+ void debug_printMystVariables(MystVariables *_tv);
+private:
+ MohawkEngine_Myst *_vm;
+ Common::SaveFileManager *_saveFileMan;
+ MystVariables *_v;
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/myst_scripts.cpp b/engines/mohawk/myst_scripts.cpp
new file mode 100644
index 0000000000..7c61bfe3c4
--- /dev/null
+++ b/engines/mohawk/myst_scripts.cpp
@@ -0,0 +1,4864 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "mohawk/myst.h"
+#include "mohawk/graphics.h"
+#include "mohawk/myst_scripts.h"
+
+#include "gui/message.h"
+
+namespace Mohawk {
+
+const uint8 stack_map[8] = {
+ kSeleniticStack,
+ kStoneshipStack,
+ kMystStack,
+ kMechanicalStack,
+ kChannelwoodStack,
+ 0x0f,
+ kDniStack,
+ kMystStack
+};
+
+const uint16 start_card[8] = {
+ 1282,
+ 2029,
+ 4396,
+ 6122,
+ 3137,
+ 0,
+ 5038,
+ 4134
+};
+
+// NOTE: Credits Start Card is 10000
+
+#define OPCODE(op, x) { op, &MystScriptParser::x, #x }
+
+MystScriptParser::MystScriptParser(MohawkEngine_Myst *vm) : _vm(vm) {
+ setupOpcodes();
+ _invokingResource = NULL;
+}
+
+MystScriptParser::~MystScriptParser() {
+}
+
+void MystScriptParser::setupOpcodes() {
+ // "invalid" opcodes do not exist or have not been observed
+ // "unknown" opcodes exist, but their meaning is unknown
+
+ static const MystOpcode myst_opcodes[] = {
+ // "Standard" Opcodes
+ OPCODE(0, toggleBoolean),
+ OPCODE(1, setVar),
+ OPCODE(2, altDest),
+ OPCODE(3, takePage),
+ OPCODE(4, opcode_4),
+ // TODO: Opcode 5 Not Present
+ OPCODE(6, opcode_6),
+ OPCODE(7, opcode_7),
+ OPCODE(8, opcode_8),
+ OPCODE(9, opcode_9),
+ // TODO: Opcode 10 to 11 Not Present
+ OPCODE(12, altDest),
+ OPCODE(13, altDest),
+ OPCODE(14, opcode_14),
+ OPCODE(15, dropPage),
+ OPCODE(16, opcode_16),
+ OPCODE(17, opcode_17),
+ OPCODE(18, opcode_18),
+ OPCODE(19, enableHotspots),
+ OPCODE(20, disableHotspots),
+ OPCODE(21, opcode_21),
+ OPCODE(22, opcode_22),
+ OPCODE(23, opcode_23),
+ OPCODE(24, playSound),
+ // TODO: Opcode 25 Not Present
+ OPCODE(26, opcode_26),
+ OPCODE(27, playSoundBlocking),
+ OPCODE(28, opcode_28),
+ OPCODE(29, opcode_29_33),
+ OPCODE(30, opcode_30),
+ OPCODE(31, opcode_31),
+ OPCODE(32, opcode_32),
+ OPCODE(33, opcode_29_33),
+ OPCODE(34, opcode_34),
+ OPCODE(35, opcode_35),
+ OPCODE(36, changeCursor),
+ OPCODE(37, hideCursor),
+ OPCODE(38, showCursor),
+ OPCODE(39, opcode_39),
+ OPCODE(40, changeStack),
+ OPCODE(41, opcode_41),
+ OPCODE(42, opcode_42),
+ OPCODE(43, opcode_43),
+ OPCODE(44, opcode_44),
+ // TODO: Opcode 45 Not Present
+ OPCODE(46, opcode_46),
+ // TODO: Opcodes 47 to 99 Not Present
+
+ // "Stack-Specific" Opcodes
+ OPCODE(100, opcode_100),
+ OPCODE(101, opcode_101),
+ OPCODE(102, opcode_102),
+ OPCODE(103, opcode_103),
+ OPCODE(104, opcode_104),
+ OPCODE(105, opcode_105),
+ OPCODE(106, opcode_106),
+ OPCODE(107, opcode_107),
+ OPCODE(108, opcode_108),
+ OPCODE(109, opcode_109),
+ OPCODE(110, opcode_110),
+ OPCODE(111, opcode_111),
+ OPCODE(112, opcode_112),
+ OPCODE(113, opcode_113),
+ OPCODE(114, opcode_114),
+ OPCODE(115, opcode_115),
+ OPCODE(116, opcode_116),
+ OPCODE(117, opcode_117),
+ OPCODE(118, opcode_118),
+ OPCODE(119, opcode_119),
+ OPCODE(120, opcode_120),
+ OPCODE(121, opcode_121),
+ OPCODE(122, opcode_122),
+ OPCODE(123, opcode_123),
+ OPCODE(124, opcode_124),
+ OPCODE(125, opcode_125),
+ OPCODE(126, opcode_126),
+ OPCODE(127, opcode_127),
+ OPCODE(128, opcode_128),
+ OPCODE(129, opcode_129),
+ OPCODE(130, opcode_130),
+ OPCODE(131, opcode_131),
+ OPCODE(132, opcode_132),
+ OPCODE(133, opcode_133),
+ // TODO: Opcodes 134 to 146 Not Present
+ OPCODE(147, opcode_147),
+ // TODO: Opcodes 148 to 163 Not Present
+ OPCODE(164, opcode_164),
+ // TODO: Opcodes 165 to 168 Not Present
+ OPCODE(169, opcode_169),
+ // TODO: Opcodes 170 to 181 Not Present
+ OPCODE(182, opcode_182),
+ OPCODE(183, opcode_183),
+ OPCODE(184, opcode_184),
+ OPCODE(185, opcode_185),
+ // TODO: Opcodes 186 to 195 Not Present
+ OPCODE(196, opcode_196), // Demo only
+ OPCODE(197, opcode_197), // Demo only
+ OPCODE(198, opcode_198),
+ OPCODE(199, opcode_199),
+
+ // "Init" Opcodes
+ OPCODE(200, opcode_200),
+ OPCODE(201, opcode_201),
+ OPCODE(202, opcode_202),
+ OPCODE(203, opcode_203),
+ OPCODE(204, opcode_204),
+ OPCODE(205, opcode_205),
+ OPCODE(206, opcode_206),
+ OPCODE(207, opcode_207),
+ OPCODE(208, opcode_208),
+ OPCODE(209, opcode_209),
+ OPCODE(210, opcode_210),
+ OPCODE(211, opcode_211),
+ OPCODE(212, opcode_212),
+ OPCODE(213, opcode_213),
+ OPCODE(214, opcode_214),
+ OPCODE(215, opcode_215),
+ OPCODE(216, opcode_216),
+ OPCODE(217, opcode_217),
+ OPCODE(218, opcode_218),
+ OPCODE(219, opcode_219),
+ OPCODE(220, opcode_220),
+ OPCODE(221, opcode_221),
+ OPCODE(222, opcode_222),
+ // TODO: Opcodes 223 to 297 Not Present
+ OPCODE(298, opcode_298), // Demo only
+ OPCODE(299, opcode_299), // Demo only
+
+ // "Exit" Opcodes
+ OPCODE(300, opcode_300),
+ OPCODE(301, opcode_301),
+ OPCODE(302, opcode_302),
+ OPCODE(303, opcode_303),
+ OPCODE(304, opcode_304),
+ OPCODE(305, opcode_305),
+ OPCODE(306, opcode_306),
+ OPCODE(307, opcode_307),
+ OPCODE(308, opcode_308),
+ OPCODE(309, opcode_309),
+ // TODO: Opcodes 310 to 311 Not Present
+ OPCODE(312, opcode_312),
+ // TODO: Opcodes 313 and greater Not Present
+
+ OPCODE(0xFFFF, NOP)
+ };
+
+ _opcodes = myst_opcodes;
+ _opcodeCount = ARRAYSIZE(myst_opcodes);
+}
+
+void MystScriptParser::disableInitOpcodes() {
+ opcode_200_disable();
+ opcode_201_disable();
+ opcode_202_disable();
+ opcode_203_disable();
+ opcode_204_disable();
+ opcode_205_disable();
+ opcode_206_disable();
+ opcode_209_disable();
+ opcode_210_disable();
+ opcode_211_disable();
+ opcode_212_disable();
+}
+
+void MystScriptParser::runPersistentOpcodes() {
+ opcode_200_run();
+ opcode_201_run();
+ opcode_202_run();
+ opcode_203_run();
+ opcode_204_run();
+ opcode_205_run();
+ opcode_206_run();
+ opcode_209_run();
+ opcode_210_run();
+ opcode_211_run();
+ opcode_212_run();
+}
+
+void MystScriptParser::runScript(uint16 scriptCount, MystScriptEntry *scripts, MystResource *invokingResource) {
+ _invokingResource = invokingResource;
+
+ debugC(kDebugScript, "Script Count: %d", scriptCount);
+ for (uint16 i = 0; i < scriptCount; i++) {
+ debugC(kDebugScript, "\tOpcode %d: %d", i, scripts[i].opcode);
+ runOpcode(scripts[i].opcode, scripts[i].var, scripts[i].numValues, scripts[i].values);
+ }
+}
+
+void MystScriptParser::runOpcode(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ bool ranOpcode = false;
+
+ for (uint16 i = 0; i < _opcodeCount; i++)
+ if (_opcodes[i].op == op) {
+ (this->*(_opcodes[i].proc)) (op, var, argc, argv);
+ ranOpcode = true;
+ break;
+ }
+
+ if (!ranOpcode)
+ error ("Trying to run invalid opcode %d", op);
+}
+
+const char *MystScriptParser::getOpcodeDesc(uint16 op) {
+ for (uint16 i = 0; i < _opcodeCount; i++)
+ if (_opcodes[i].op == op)
+ return _opcodes[i].desc;
+
+ error("Unknown opcode %d", op);
+ return "";
+}
+
+// NOTE: Check to be used on Opcodes where var is thought
+// not to be used. This emits a warning if var is nonzero.
+// It is possible that the opcode does use var 0 in this case,
+// but this will catch the majority of missed cases.
+void MystScriptParser::varUnusedCheck(uint16 op, uint16 var) {
+ if (var != 0)
+ warning("Opcode %d: Unused Var %d", op, var);
+}
+
+void MystScriptParser::unknown(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ // NOTE: printf used here instead of debug, so unknown opcodes are always reported...
+ printf("Unimplemented opcode 0x%02x (%d)\n", op, op);
+ printf("\tUses var %d\n", var);
+ printf("\tArg count = %d\n", argc);
+ if (argc)
+ printf("\tArgs: ");
+ for (uint16 i = 0; i < argc; i++) {
+ if (i == argc - 1)
+ printf("%d\n", argv[i]);
+ else
+ printf("%d, ", argv[i]);
+ }
+}
+
+void MystScriptParser::NOP(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ // NOTE: Don't check argc/argv here as they vary depending on NOP erased opcode
+ debugC(kDebugScript, "NOP");
+}
+
+void MystScriptParser::toggleBoolean(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: toggleBoolean() var %d", op, var);
+ // HACK: This Mech Card seems to be a special case... Are there others,
+ // or a more general definition of this opcode?
+ if (_vm->getCurStack() == kMechanicalStack && _vm->getCurCard() == 6267)
+ _vm->_varStore->setVar(var, (_vm->_varStore->getVar(var) + 1) % 10);
+ else
+ _vm->_varStore->setVar(var, !_vm->_varStore->getVar(var));
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::setVar(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ if (argc == 1) {
+ debugC(kDebugScript, "Opcode %d: setVar var %d = %d", op, var, argv[0]);
+
+ _vm->_varStore->setVar(var, argv[0]);
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::altDest(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ if (argc == 1) {
+ // TODO: Work out any differences between opcode 2, 12 and 13..
+ debugC(kDebugScript, "Opcode %d: altDest var %d: %d", op, var, _vm->_varStore->getVar(var));
+
+ if (_vm->_varStore->getVar(var))
+ _vm->changeToCard(argv[0]);
+ else if (_invokingResource != NULL)
+ _vm->changeToCard(_invokingResource->getDest());
+ else
+ warning("Missing invokingResource in altDest call");
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::takePage(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ if (argc == 1) {
+ uint16 cursorId = argv[0];
+
+ debugC(kDebugScript, "Opcode %d: takePage Var %d CursorId %d", op, var, cursorId);
+
+ if (_vm->_varStore->getVar(var)) {
+ _vm->setMainCursor(cursorId);
+
+ _vm->_varStore->setVar(var, 0);
+
+ // Return pages that are already held
+ if (var == 102)
+ _vm->_varStore->setVar(103, 1);
+
+ if (var == 103)
+ _vm->_varStore->setVar(102, 1);
+ }
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_4(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ // Used on Exit Script of Mechanical Card 6044 (Fortress Rotation Simulator)
+
+ if (argc == 0 && _vm->getCurStack() == kSeleniticStack && _vm->getCurCard() == 1275) {
+ // TODO: Fixes Selenitic Card 1275, but not sure if this is correct for general case..
+ // as it breaks Selenitic Card 1257, though this may be due to screen update. Also,
+ // this may actually be a "Force Card Reload" or "VIEW conditional re-evaluation".. in
+ // the general case, rather than this image blit...
+ uint16 var_value = _vm->_varStore->getVar(var);
+ if (var_value < _vm->_view.scriptResCount) {
+ if (_vm->_view.scriptResources[var_value].type == 1) // TODO: Add Symbols for Types
+ _vm->_gfx->copyImageToScreen(_vm->_view.scriptResources[var_value].id, Common::Rect(0, 0, 544, 333));
+ else
+ warning("Opcode %d: Script Resource %d Type Not Image", op, var_value);
+ } else
+ warning("Opcode %d: var %d value %d outside Script Resource Range %d", op, var, var_value, _vm->_view.scriptResCount);
+ } else
+ unknown(op, var, argc, argv);
+}
+
+// TODO: Work out difference between Opcode 6, 7 & 8...
+
+void MystScriptParser::opcode_6(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 0) {
+ // Used for Selenitic Card 1286 Resource #0
+ // Used for Myst Card 4143 Resource #0 & #5
+ debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op);
+
+ if (_invokingResource != NULL)
+ _vm->changeToCard(_invokingResource->getDest());
+ else
+ warning("Opcode %d: Missing invokingResource", op);
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_7(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ if (argc == 0) {
+ // Used for Selenitic Card 1244 Resource #3 Var = 5 (Sound Receiver Doors)
+ // Used for Myst Card 4143 Resource #1 & #6
+ debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op);
+ debugC(kDebugScript, "\tVar: %d", var);
+ // TODO: Var used (if non-zero?) in some way to control function...
+
+ if (_invokingResource != NULL)
+ _vm->changeToCard(_invokingResource->getDest());
+ else
+ warning("Opcode %d: Missing invokingResource", op);
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_8(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ if (argc == 0) {
+ // Used for Selenitic Card 1244 Resource #2 Var = 5 (Sound Receiver Doors)
+ debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op);
+ debugC(kDebugScript, "\tVar: %d", var);
+ // TODO: Var used (if non-zero?) in some way to control function...
+
+ if (_invokingResource != NULL)
+ _vm->changeToCard(_invokingResource->getDest());
+ else
+ warning("Opcode %d: Missing invokingResource", op);
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_9(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 0 || argc == 1) {
+ debugC(kDebugScript, "Opcode %d: Trigger Type 6 Resource Movie..", op);
+ // TODO: Add Logic to do this...
+
+ // Used on Stoneship Card 2138 with 1 argument of 66535 as well as with
+ // no arguments. Seems logically consistent with play movie with optional
+ // start point or time direction control?
+
+ // This understanding of this opcode is based upon Stoneship Card 2197
+ // i.e. Sirrus' Desk, but since this is a single case, we should find
+ // more...
+ if (!((_vm->getCurStack() == kStoneshipStack && _vm->getCurCard() == 2197) ||
+ (_vm->getCurStack() == kStoneshipStack && _vm->getCurCard() == 2138)))
+ warning("TODO: Opcode 9 on this card - Check function is consistent");
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_14(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ if (argc == 1) {
+ debugC(kDebugScript, "Opcode %d: Unknown, 1 Argument: %d", op, argv[0]);
+ debugC(kDebugScript, "\tVar: %d", var);
+
+ // TODO: Function Unknown...
+ // Function looks like it changes the Var8 of the invoking resource to argument value..
+ // Most calls seem to have var = 0, but used in Myst Card 4500 (Execute Button)
+ // with Var 105..
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::dropPage(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: dropPage", op);
+ debugC(kDebugScript, "\tvar: %d", var);
+
+ // TODO: Need to check where this is used
+ _vm->_varStore->setVar(var, 1);
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_16(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ // Used by Channelwood Card 3262 (In Elevator)
+ if (argc == 2) {
+ debugC(kDebugScript, "Opcode %d: Change Card? Conditional?", op);
+
+ uint16 cardId = argv[0];
+ uint16 u0 = argv[1];
+
+ debugC(kDebugScript, "\tcardId: %d", cardId);
+ debugC(kDebugScript, "\tu0: %d", u0);
+
+ // TODO: Finish Implementation...
+ _vm->changeToCard(cardId);
+ } else
+ unknown(op, var, argc, argv);
+}
+
+// NOTE: Opcode 17 and 18 form a pair, where Opcode 17 jumps to a card,
+// but with the current cardId stored.
+// Opcode 18 then "pops" this stored CardId and returns to that card.
+
+// TODO: The purpose of the optional argv[1] on Opcode 17 and argv[0]
+// on Opcode 18 which are always 4, 5 or 6 is unknown.
+
+static uint16 opcode_17_18_cardId = 0;
+
+void MystScriptParser::opcode_17(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 2) {
+ debugC(kDebugScript, "Opcode %d: Jump to Card Id, Storing Current Card Id", op);
+
+ uint16 cardId = argv[0];
+ debugC(kDebugScript, "\tJump to CardId: %d", cardId);
+
+ uint16 u0 = argv[1]; // TODO
+ debugC(kDebugScript, "\tu0: %d", u0);
+
+ opcode_17_18_cardId = _vm->getCurCard();
+
+ debugC(kDebugScript, "\tCurrent CardId: %d", opcode_17_18_cardId);
+
+ _vm->changeToCard(cardId);
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_18(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 1) {
+ debugC(kDebugScript, "Opcode %d: Return To Stored Card Id", op);
+ debugC(kDebugScript, "\tCardId: %d", opcode_17_18_cardId);
+
+ uint16 u0 = argv[0];
+ debugC(kDebugScript, "\tu0: %d", u0);
+
+ _vm->changeToCard(opcode_17_18_cardId);
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::enableHotspots(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc > 0) {
+ debugC(kDebugScript, "Opcode %d: Enable Hotspots", op);
+
+ uint16 count = argv[0];
+
+ if (argc != count + 1)
+ unknown(op, var, argc, argv);
+ else {
+ for (uint16 i = 0; i < count; i++) {
+ debugC(kDebugScript, "Enable hotspot index %d", argv[i + 1]);
+ _vm->setResourceEnabled(argv[i + 1], true);
+ }
+ }
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::disableHotspots(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc > 0) {
+ debugC(kDebugScript, "Opcode %d: Disable Hotspots", op);
+
+ uint16 count = argv[0];
+
+ if (argc != count + 1)
+ unknown(op, var, argc, argv);
+ else {
+ for (uint16 i = 0; i < count; i++) {
+ debugC(kDebugScript, "Disable hotspot index %d", argv[i + 1]);
+ if (argv[i + 1] == 0xFFFF) {
+ if (_invokingResource != NULL)
+ _invokingResource->setEnabled(false);
+ else
+ warning("Unknown Resource in disableHotspots script Opcode");
+ } else
+ _vm->setResourceEnabled(argv[i + 1], false);
+ }
+ }
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_21(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 6) {
+ // Used in Channelwood Card 3318 (Sirrus' Room Drawer)
+ debugC(kDebugScript, "Opcode %d: Vertical Slide?", op);
+
+ Common::Rect rect1 = Common::Rect(argv[0], argv[1], argv[2], argv[3]);
+ uint16 u0 = argv[4];
+ uint16 u1 = argv[5];
+
+ debugC(kDebugScript, "\trect1.left: %d", rect1.left);
+ debugC(kDebugScript, "\trect1.top: %d", rect1.top);
+ debugC(kDebugScript, "\trect1.right: %d", rect1.right);
+ debugC(kDebugScript, "\trect1.bottom: %d", rect1.bottom);
+
+ debugC(kDebugScript, "\tu0: %d", u0);
+ debugC(kDebugScript, "\tu1: %d", u1);
+
+ // TODO: Complete Implementation...
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_22(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 0) {
+ if (_invokingResource != NULL)
+ _vm->changeToCard(_invokingResource->getDest());
+ else
+ warning("Missing invokingResource in opcode_22 call");
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_23(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 2 || argc == 4) {
+ debugC(kDebugScript, "Opcode %d: Change Resource Enable States", op);
+
+ // Used on Stoneship Card 2080 (Lit Ship Cabin Facing Myst Book Table)
+ // Called when Table is clicked to extrude book.
+
+ // Used on Mechanical Card 6159 (In Front of Staircase to Elevator Control)
+ // Called when Button Pressed.
+
+ for (byte i = 0; i < argc; i++) {
+ debugC(kDebugScript, "\tResource %d Enable set to %d", i, argv[i]);
+ switch (argv[i]) {
+ case 0:
+ _vm->setResourceEnabled(i, false);
+ break;
+ case 1:
+ _vm->setResourceEnabled(i, true);
+ break;
+ default:
+ warning("Opcode %d u%d non-boolean", op, i);
+ _vm->setResourceEnabled(i, true);
+ break;
+ }
+ }
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::playSound(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 1) {
+ uint16 soundId = argv[0];
+
+ debugC(kDebugScript, "Opcode %d: playSound", op);
+ debugC(kDebugScript, "\tsoundId: %d", soundId);
+
+ _vm->_sound->playSound(soundId);
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_26(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Unknown...", op);
+
+ // TODO: Work out function...
+ if (_vm->getCurStack() == kSeleniticStack && _vm->getCurCard() == 1245) {
+ debugC(kDebugScript, "TODO: Function Not Known... Used by Exit Hotspot Resource");
+ } else if (_vm->getCurStack() == kStoneshipStack && _vm->getCurCard() == 2226) {
+ debugC(kDebugScript, "TODO: Function Not Known... Used by Ship Cabin Door");
+ } else if (_vm->getCurStack() == kStoneshipStack && _vm->getCurCard() == 2294) {
+ debugC(kDebugScript, "TODO: Function Not Known... Used by Sirrus' Room Door");
+ } else if (_vm->getCurStack() == kMechanicalStack && _vm->getCurCard() == 6327) {
+ debugC(kDebugScript, "TODO: Function Not Known... Used by Elevator");
+ } else if (_vm->getCurStack() == kDniStack && _vm->getCurCard() == 5014) {
+ debugC(kDebugScript, "TODO: Function Not Known... Used by Atrus");
+ } else
+ unknown(op, var, argc, argv);
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::playSoundBlocking(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 1) {
+ uint16 soundId = argv[0];
+
+ debugC(kDebugScript, "Opcode %d: playSoundBlocking", op);
+ debugC(kDebugScript, "\tsoundId: %d", soundId);
+
+ Audio::SoundHandle *handle = _vm->_sound->playSound(soundId);
+
+ while (_vm->_mixer->isSoundHandleActive(*handle))
+ _vm->_system->delayMillis(10);
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_28(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ Common::Rect rect;
+
+ if (argc == 1 || argc == 4) {
+ debugC(kDebugScript, "Opcode %d: Restore VIEW Default Image in Region", op);
+
+ if (argc == 1) {
+ // Used in Stoneship Card 2111 (Compass Rose)
+ // Used in Mechanical Card 6267 (Code Lock)
+ if (argv[0] == 0xFFFF) {
+ rect = _invokingResource->getRect();
+ } else
+ unknown(op, var, argc, argv);
+ } else if (argc == 4) {
+ // Used in ... TODO: Fill in.
+ rect = Common::Rect(argv[0], argv[1], argv[2], argv[3]);
+ } else
+ warning("Opcode %d: argc Error", op);
+
+ debugC(kDebugScript, "\trect.left: %d", rect.left);
+ debugC(kDebugScript, "\trect.top: %d", rect.top);
+ debugC(kDebugScript, "\trect.right: %d", rect.right);
+ debugC(kDebugScript, "\trect.bottom: %d", rect.bottom);
+
+ // TODO: Need to fix VIEW logic so this doesn't need
+ // calculation at this level.
+ uint16 imageToDraw;
+ if (_vm->_view.conditionalImageCount == 0)
+ imageToDraw = _vm->_view.mainImage;
+ else {
+ for (uint16 i = 0; i < _vm->_view.conditionalImageCount; i++)
+ if (_vm->_varStore->getVar(_vm->_view.conditionalImages[i].var) < _vm->_view.conditionalImages[i].numStates)
+ imageToDraw = _vm->_view.conditionalImages[i].values[_vm->_varStore->getVar(_vm->_view.conditionalImages[i].var)];
+ }
+ _vm->_gfx->copyImageSectionToScreen(imageToDraw, rect, rect);
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_29_33(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ // TODO: Opcode 29 called on Mechanical Card 6178 causes a engine
+ // abort this is because imageId is 7158 (not valid), but the
+ // script resource gives this as 7178 (valid)...
+
+ if (argc == 7) {
+ uint16 imageId = argv[0];
+
+ Common::Rect srcRect = Common::Rect(argv[1], argv[2], argv[3], argv[4]);
+
+ Common::Rect dstRect = Common::Rect(argv[5], argv[6], 544, 333);
+
+ if (dstRect.left == -1 || dstRect.top == -1) {
+ // Interpreted as full screen
+ dstRect.left = 0;
+ dstRect.top = 0;
+ }
+
+ dstRect.right = dstRect.left + srcRect.width();
+ dstRect.bottom = dstRect.top + srcRect.height();
+
+ debugC(kDebugScript, "Opcode %d: Blit Image", op);
+ debugC(kDebugScript, "\timageId: %d", imageId);
+ debugC(kDebugScript, "\tsrcRect.left: %d", srcRect.left);
+ debugC(kDebugScript, "\tsrcRect.top: %d", srcRect.top);
+ debugC(kDebugScript, "\tsrcRect.right: %d", srcRect.right);
+ debugC(kDebugScript, "\tsrcRect.bottom: %d", srcRect.bottom);
+ debugC(kDebugScript, "\tdstRect.left: %d", dstRect.left);
+ debugC(kDebugScript, "\tdstRect.top: %d", dstRect.top);
+ debugC(kDebugScript, "\tdstRect.right: %d", dstRect.right);
+ debugC(kDebugScript, "\tdstRect.bottom: %d", dstRect.bottom);
+
+ _vm->_gfx->copyImageSectionToScreen(imageId, srcRect, dstRect);
+ } else
+ unknown(op, var, argc, argv);
+}
+
+// TODO: Implement common engine function for read and processing of sound blocks
+// for use by this opcode and VIEW sound block.
+// TODO: Though the playSound and PlaySoundBlocking opcodes play sounds immediately,
+// this opcode changes the main background sound playing..
+// Current behaviour here and with VIEW sound block is not right as demonstrated
+// by Channelwood Card 3280 (Tank Valve) and water flow sound behaviour in pipe
+// on cards leading from shed...
+void MystScriptParser::opcode_30(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ int16 *soundList = NULL;
+ uint16 *soundListVolume = NULL;
+
+ // Used on Stoneship Card 2080
+ // Used on Channelwood Card 3225 with argc = 8 i.e. Conditional Sound List
+ if (argc == 1 || argc == 2 || argc == 8) {
+ debugC(kDebugScript, "Opcode %d: Process Sound Block", op);
+ uint16 decodeIdx = 0;
+
+ int16 soundAction = argv[decodeIdx++];
+ uint16 soundVolume = 65535;
+ if (soundAction == kMystSoundActionChangeVolume || soundAction > 0) {
+ soundVolume = argv[decodeIdx++];
+ } else if (soundAction == kMystSoundActionConditional) {
+ debugC(kDebugScript, "Conditional sound list");
+ uint16 condVar = argv[decodeIdx++];
+ uint16 condVarValue = _vm->_varStore->getVar(condVar);
+ uint16 condCount = argv[decodeIdx++];
+
+ debugC(kDebugScript, "\tcondVar: %d = %d", condVar, condVarValue);
+ debugC(kDebugScript, "\tcondCount: %d", condCount);
+
+ soundList = new int16[condCount];
+ soundListVolume = new uint16[condCount];
+
+ if (condVarValue >= condCount)
+ warning("Opcode %d: Conditional sound variable outside range", op);
+ else {
+ for (uint16 i = 0; i < condCount; i++) {
+ soundList[i] = argv[decodeIdx++];
+ debugC(kDebugScript, "\t\tCondition %d: Action %d", i, soundList[i]);
+ if (soundAction == kMystSoundActionChangeVolume || soundAction > 0) {
+ soundListVolume[i] = argv[decodeIdx++];
+ } else
+ soundListVolume[i] = 65535;
+ debugC(kDebugScript, "\t\tCondition %d: Volume %d", i, soundListVolume[i]);
+ }
+
+ soundAction = soundList[condVarValue];
+ soundVolume = soundListVolume[condVarValue];
+ }
+ }
+
+ // NOTE: Mixer only has 8-bit channel volume granularity,
+ // Myst uses 16-bit? Or is part of this balance?
+ soundVolume = (byte)(soundVolume / 255);
+
+ if (soundAction == kMystSoundActionContinue)
+ debugC(kDebugScript, "Continue current sound");
+ else if (soundAction == kMystSoundActionChangeVolume) {
+ debugC(kDebugScript, "Continue current sound, change volume");
+ debugC(kDebugScript, "\tVolume: %d", soundVolume);
+ // TODO: Implement Volume Control..
+ } else if (soundAction == kMystSoundActionStop) {
+ debugC(kDebugScript, "Stop sound");
+ _vm->_sound->stopSound();
+ } else if (soundAction > 0) {
+ debugC(kDebugScript, "Play new Sound, change volume");
+ debugC(kDebugScript, "\tSound: %d", soundAction);
+ debugC(kDebugScript, "\tVolume: %d", soundVolume);
+ _vm->_sound->stopSound();
+ // TODO: Need to keep sound handle and add function to change volume of
+ // looped running sound for kMystSoundActionChangeVolume type
+ _vm->_sound->playSound(soundAction, true, soundVolume);
+ } else {
+ debugC(kDebugScript, "Unknown");
+ warning("Unknown sound control value in opcode %d", op);
+ }
+ } else
+ unknown(op, var, argc, argv);
+
+ delete[] soundList;
+ soundList = NULL;
+ delete[] soundListVolume;
+ soundListVolume = NULL;
+}
+
+void MystScriptParser::opcode_31(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ // Used on Channelwood Card 3505 (Walkway from Sirrus' Room)
+ if (argc == 2) {
+ debugC(kDebugScript, "Opcode %d: Boolean Choice of Play Sound", op);
+
+ uint16 soundId0 = argv[0];
+ uint16 soundId1 = argv[1];
+
+ debugC(kDebugScript, "\tvar: %d", var);
+ debugC(kDebugScript, "\tsoundId0: %d", soundId0);
+ debugC(kDebugScript, "\tsoundId1: %d", soundId1);
+
+ if (_vm->_varStore->getVar(var)) {
+ if (soundId1)
+ _vm->_sound->playSound(soundId1);
+ } else {
+ if (soundId0)
+ _vm->_sound->playSound(soundId0);
+ }
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_32(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ // Used on Channelwood Card 3503 (Door to Sirrus' Room)
+ // Used on Myst Card 4188 (Door to Cabin)
+ // Used on Myst Card 4363 (Red Book Open)
+ // Used on Myst Card 4371 (Blue Book Open)
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Unknown...", op);
+ // TODO: Implement function...
+ // Set Resource 0 Enabled?
+ // or Trigger Movie?
+ // Set resource flag to Enabled?
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_34(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 2) {
+ debugC(kDebugScript, "Opcode %d: Change Card (with Delay?)", op);
+
+ uint16 cardId = argv[0];
+ uint16 u0 = argv[1];
+
+ debugC(kDebugScript, "\tTarget Card: %d", cardId);
+ debugC(kDebugScript, "\tu0: %d", u0); // TODO: Delay?
+
+ _vm->changeToCard(cardId);
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_35(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 3) {
+ debugC(kDebugScript, "Opcode %d: Draw Full Screen Image, Delay then Change Card", op);
+
+ uint16 imageId = argv[0];
+ uint16 cardId = argv[1];
+ uint16 delay = argv[2]; // TODO: Not sure about argv[2] being delay..
+
+ debugC(kDebugScript, "\timageId: %d", imageId);
+ debugC(kDebugScript, "\tcardId: %d", cardId);
+ debugC(kDebugScript, "\tdelay: %d", delay);
+
+ _vm->_gfx->copyImageToScreen(imageId, Common::Rect(0, 0, 544, 333));
+ _vm->_system->updateScreen();
+ _vm->_system->delayMillis(delay * 100);
+ _vm->changeToCard(cardId);
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::changeCursor(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 1) {
+ debugC(kDebugScript, "Opcode %d: Change Cursor", op);
+ debugC(kDebugScript, "Cursor: %d", argv[0]);
+
+ // TODO: Not sure if this needs to change mainCursor or similar...
+ _vm->_gfx->changeCursor(argv[0]);
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::hideCursor(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Hide Cursor", op);
+ _vm->_gfx->hideCursor();
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::showCursor(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Show Cursor", op);
+ _vm->_gfx->showCursor();
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_39(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 1) {
+ // Used on Mechanical Card 6327 (Elevator)
+ debugC(kDebugScript, "Opcode %d: Delay?", op);
+
+ uint16 time = argv[0];
+
+ debugC(kDebugScript, "\tTime: %d", time);
+
+ // TODO: Fill in Function...
+ // May actually be related to movie control.. not sure.
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::changeStack(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ Audio::SoundHandle *handle;
+ varUnusedCheck(op, var);
+
+ if (argc == 3) {
+ debugC(kDebugScript, "Opcode %d: changeStack", op);
+
+ uint16 targetStack = argv[0];
+ uint16 soundIdLinkSrc = argv[1];
+ uint16 soundIdLinkDst = argv[2];
+
+ debugC(kDebugScript, "\tTarget Stack: %d", targetStack);
+ debugC(kDebugScript, "\tSource Stack Link Sound: %d", soundIdLinkSrc);
+ debugC(kDebugScript, "\tDestination Stack Link Sound: %d", soundIdLinkDst);
+
+ _vm->_sound->stopSound();
+
+ if (_vm->getFeatures() & GF_DEMO) {
+
+ // The demo has linking sounds too for this, but it just sounds completely
+ // wrong as you're not actually linking when using this opcode. The sounds are only
+ // played for the full game linking.
+ if (!_vm->_tweaksEnabled) {
+ handle= _vm->_sound->playSound(soundIdLinkSrc);
+ while (_vm->_mixer->isSoundHandleActive(*handle))
+ _vm->_system->delayMillis(10);
+ }
+
+ // No need to have a table for just this data...
+ if (targetStack == 1) {
+ _vm->changeToStack(kDemoSlidesStack);
+ _vm->changeToCard(1000);
+ } else if (targetStack == 2) {
+ _vm->changeToStack(kDemoPreviewStack);
+ _vm->changeToCard(3000);
+ }
+
+ if (!_vm->_tweaksEnabled) {
+ handle = _vm->_sound->playSound(soundIdLinkDst);
+ while (_vm->_mixer->isSoundHandleActive(*handle))
+ _vm->_system->delayMillis(10);
+ }
+ } else {
+ handle = _vm->_sound->playSound(soundIdLinkSrc);
+ while (_vm->_mixer->isSoundHandleActive(*handle))
+ _vm->_system->delayMillis(10);
+
+ // TODO: Play Flyby Entry Movie on Masterpiece Edition..? Only on Myst to Age Link?
+ _vm->changeToStack(stack_map[targetStack]);
+ _vm->changeToCard(start_card[targetStack]);
+
+ handle = _vm->_sound->playSound(soundIdLinkDst);
+ while (_vm->_mixer->isSoundHandleActive(*handle))
+ _vm->_system->delayMillis(10);
+ }
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_41(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ // TODO: Will need to stop immediate screen update on
+ // Opcode 29 etc. for this to work correctly..
+ // Also, script processing will have to block U/I
+ // events etc. for correct sequencing.
+
+ if (argc == 10 || argc == 16) {
+ uint16 cardId = argv[0];
+ uint16 soundId = argv[1];
+ uint16 u0 = argv[2];
+ uint16 u1 = argv[3];
+ Common::Rect region = Common::Rect(argv[4], argv[5], argv[6], argv[7]);
+ uint16 updateDirection = argv[8];
+ uint16 u2 = argv[9];
+
+ Common::Rect region2;
+ uint16 updateDirection2;
+ uint16 u3;
+ if (argc == 16) {
+ region2 = Common::Rect(argv[10], argv[11], argv[12], argv[13]);
+ updateDirection2 = argv[14];
+ u3 = argv[15];
+ }
+
+ debugC(kDebugScript, "Opcode %d: Change Card, Play Sound and Directional Update Screen Region", op);
+ debugC(kDebugScript, "\tCard Id: %d", cardId);
+ debugC(kDebugScript, "\tSound Id: %d", soundId);
+ debugC(kDebugScript, "\tu0: %d", u0);
+ debugC(kDebugScript, "\tu1: %d", u1);
+ debugC(kDebugScript, "\tregion.left: %d", region.left);
+ debugC(kDebugScript, "\tregion.top: %d", region.top);
+ debugC(kDebugScript, "\tregion.right: %d", region.right);
+ debugC(kDebugScript, "\tregion.bottom: %d", region.bottom);
+ debugCN(kDebugScript, "\tupdateDirection: %d = ", updateDirection);
+
+ switch (updateDirection) {
+ case 0:
+ debugC(kDebugScript, "Left to Right");
+ break;
+ case 1:
+ debugC(kDebugScript, "Right to Left");
+ break;
+ case 5:
+ debugC(kDebugScript, "Top to Bottom");
+ break;
+ case 6:
+ debugC(kDebugScript, "Bottom to Top");
+ break;
+ default:
+ warning("Unknown Update Direction");
+ break;
+ }
+
+ debugC(kDebugScript, "\tu2: %d", u2); // TODO: Speed / Delay of Update?
+
+ // 10 Argument version Used in:
+ // Selenitic Card 1243 (Sound Receiver Door)
+ // Myst Card 4317 (Generator Room Door)
+
+ if (argc == 16) {
+ // 16 Argument version Used in:
+ // Selenitic Card 1008 and 1010 (Mazerunner Door)
+
+ debugC(kDebugScript, "\tregion2.left: %d", region2.left);
+ debugC(kDebugScript, "\tregion2.top: %d", region2.top);
+ debugC(kDebugScript, "\tregion2.right: %d", region2.right);
+ debugC(kDebugScript, "\tregion2.bottom: %d", region2.bottom);
+ debugCN(kDebugScript, "\tupdateDirection2: %d = ", updateDirection2);
+
+ switch (updateDirection2) {
+ case 0:
+ debugC(kDebugScript, "Left to Right");
+ break;
+ case 1:
+ debugC(kDebugScript, "Right to Left");
+ break;
+ case 5:
+ debugC(kDebugScript, "Top to Bottom");
+ break;
+ case 6:
+ debugC(kDebugScript, "Bottom to Top");
+ break;
+ default:
+ warning("Unknown Update Direction");
+ break;
+ }
+
+ debugC(kDebugScript, "\tu3: %d", u3); // TODO: Speed / Delay of Update?
+ }
+
+ _vm->changeToCard(cardId);
+ _vm->_sound->playSound(soundId);
+ // TODO: Complete Implementation
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_42(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ // TODO: Will need to stop immediate screen update on
+ // Opcode 29 etc. for this to work correctly..
+ // Also, script processing will have to block U/I
+ // events etc. for correct sequencing.
+
+ if (argc == 9 || argc == 15) {
+ uint16 soundId = argv[0];
+ uint16 u0 = argv[1];
+ uint16 u1 = argv[2];
+ Common::Rect region = Common::Rect(argv[3], argv[4], argv[5], argv[6]);
+ uint16 updateDirection = argv[7];
+ uint16 u2 = argv[8];
+
+ Common::Rect region2;
+ uint16 updateDirection2;
+ uint16 u3;
+ if (argc == 15) {
+ region2 = Common::Rect(argv[9], argv[10], argv[11], argv[12]);
+ updateDirection2 = argv[13];
+ u3 = argv[14];
+ }
+
+ debugC(kDebugScript, "Opcode %d: Play Sound and Directional Update Screen Region", op);
+ debugC(kDebugScript, "\tsound: %d", soundId);
+ debugC(kDebugScript, "\tu0: %d", u0);
+ debugC(kDebugScript, "\tu1: %d", u1);
+ debugC(kDebugScript, "\tregion.left: %d", region.left);
+ debugC(kDebugScript, "\tregion.top: %d", region.top);
+ debugC(kDebugScript, "\tregion.right: %d", region.right);
+ debugC(kDebugScript, "\tregion.bottom: %d", region.bottom);
+ debugCN(kDebugScript, "\tupdateDirection: %d = ", updateDirection);
+
+ switch (updateDirection) {
+ case 0:
+ debugC(kDebugScript, "Left to Right");
+ break;
+ case 1:
+ debugC(kDebugScript, "Right to Left");
+ break;
+ case 5:
+ debugC(kDebugScript, "Top to Bottom");
+ break;
+ case 6:
+ debugC(kDebugScript, "Bottom to Top");
+ break;
+ default:
+ warning("Unknown Update Direction");
+ break;
+ }
+
+ debugC(kDebugScript, "\tu2: %d", u2); // TODO: Speed / Delay of Update?
+
+ // 9 Argument version Used in:
+ // Myst Card 4730 (Stellar Observatory Door)
+ // Myst Card 4184 (Cabin Door)
+
+ if (argc == 15) {
+ // 15 Argument version Used in:
+ // Channelwood Card 3492 (Achenar's Room Door)
+
+ debugC(kDebugScript, "\tregion2.left: %d", region2.left);
+ debugC(kDebugScript, "\tregion2.top: %d", region2.top);
+ debugC(kDebugScript, "\tregion2.right: %d", region2.right);
+ debugC(kDebugScript, "\tregion2.bottom: %d", region2.bottom);
+ debugCN(kDebugScript, "\tupdateDirection2: %d = ", updateDirection2);
+
+ switch (updateDirection2) {
+ case 0:
+ debugC(kDebugScript, "Left to Right");
+ break;
+ case 1:
+ debugC(kDebugScript, "Right to Left");
+ break;
+ case 5:
+ debugC(kDebugScript, "Top to Bottom");
+ break;
+ case 6:
+ debugC(kDebugScript, "Bottom to Top");
+ break;
+ default:
+ warning("Unknown Update Direction");
+ break;
+ }
+
+ debugC(kDebugScript, "\tu3: %d", u3); // TODO: Speed / Delay of Update?
+ }
+
+ _vm->_sound->playSound(soundId);
+ // TODO: Complete Implementation
+ } else
+ unknown(op, var, argc, argv);
+}
+
+// TODO: Are Opcode 43 and 44 enable / disable paired commands?
+
+void MystScriptParser::opcode_43(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Unknown Function", op);
+
+ // TODO: Function Unknown
+ // Used on Stoneship Card 2154 (Bottom of Lighthouse)
+ // Used on Stoneship Card 2138 (Lighthouse Floating Chest Closeup)
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_44(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Unknown Function", op);
+
+ // TODO: Function Unknown
+ // Used on Stoneship Card 2154 (Bottom of Lighthouse)
+ // Used on Stoneship Card 2138 (Lighthouse Floating Chest Closeup)
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_46(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ if (argc == 0) {
+ // Used on Selenitic Card 1191 (Maze Runner)
+ // Used on Mechanical Card 6267 (Code Lock)
+ // Used when Button is pushed...
+ debugC(kDebugScript, "Opcode %d: Conditional Code Jump?", op);
+ // TODO: Function Unknown - Fill in...
+ // Logic looks like this is some kind of Conditional Code
+ // Jump Point.
+ // The Logic for the Mechanical Code Lock Seems to be in this
+ // opcode with it being present twice delimiting the start
+ // of the incorrect code and correct code action blocks...
+ // Not sure how a general case can be made for this..
+ } else
+ unknown(op, var, argc, argv);
+}
+
+void MystScriptParser::opcode_100(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ // Hard coded SoundId valid only for Intro Stack.
+ // Other stacks use Opcode 40, which takes SoundId values as arguments.
+ const uint16 soundIdLinkSrc = 5;
+
+ switch (_vm->getCurStack()) {
+ case kIntroStack:
+ debugC(kDebugScript, "Opcode %d: ChangeStack", op);
+ debugC(kDebugScript, "\tvar: %d", var);
+
+ // TODO: Merge with changeStack (Opcode 40) Implementation?
+ if (_vm->_varStore->getVar(var) == 5 || _vm->_varStore->getVar(var) > 7) {
+ // TODO: Dead Book i.e. Released Sirrus/Achenar
+ } else {
+ // Play Linking Sound, blocking...
+ _vm->_sound->stopSound();
+ Audio::SoundHandle *handle = _vm->_sound->playSound(soundIdLinkSrc);
+ while (_vm->_mixer->isSoundHandleActive(*handle))
+ _vm->_system->delayMillis(10);
+
+ // Play Flyby Entry Movie on Masterpiece Edition..
+ if ((_vm->getFeatures() & GF_ME)) {
+ switch (stack_map[_vm->_varStore->getVar(var)]) {
+ case kSeleniticStack:
+ _vm->_video->playMovieCentered(_vm->wrapMovieFilename("selenitic flyby", kMasterpieceOnly));
+ break;
+ case kStoneshipStack:
+ _vm->_video->playMovieCentered(_vm->wrapMovieFilename("stoneship flyby", kMasterpieceOnly));
+ break;
+ // Myst Flyby Movie not used in Original Masterpiece Edition Engine
+ case kMystStack:
+ if (_vm->_tweaksEnabled)
+ _vm->_video->playMovieCentered(_vm->wrapMovieFilename("myst flyby", kMasterpieceOnly));
+ break;
+ case kMechanicalStack:
+ _vm->_video->playMovieCentered(_vm->wrapMovieFilename("mech age flyby", kMasterpieceOnly));
+ break;
+ case kChannelwoodStack:
+ _vm->_video->playMovieCentered(_vm->wrapMovieFilename("channelwood flyby", kMasterpieceOnly));
+ break;
+ default:
+ break;
+ }
+ }
+
+ uint16 varValue = _vm->_varStore->getVar(var);
+ _vm->changeToStack(stack_map[varValue]);
+ _vm->changeToCard(start_card[varValue]);
+
+ // TODO: No soundIdLinkDst for Opcode 100 link? Check Original.
+ }
+ break;
+ case kStoneshipStack:
+ // Used for Cards 2185 (Water Pump)
+ varUnusedCheck(op, var);
+
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Unknown Function", op);
+
+ // TODO: Called when Water Pump Button is pressed? Animation?
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kDniStack:
+ // Used in Card 5022 (Rocks)
+ varUnusedCheck(op, var);
+
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Unknown Function", op);
+
+ // TODO: Fill in Logic.
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kCreditsStack:
+ case kMakingOfStack:
+ _vm->_system->quit();
+ break;
+ case kDemoSlidesStack:
+ // TODO: Change to changeStack call?
+ _vm->changeToStack(kDemoStack);
+ _vm->changeToCard(2001);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_101(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ varUnusedCheck(op, var);
+
+ if (argc == 0) {
+ // Used on Card 1191 (Maze Runner)
+ // Called when Red Warning Button is pushed
+
+ debugC(kDebugScript, "Opcode %d: Repeat Buzzer Sound?", op);
+
+ // TODO: Fill in logic...
+ // Repeat buzzer sound
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kStoneshipStack:
+ varUnusedCheck(op, var);
+
+ if (argc == 6) {
+ // Used by Door Buttons to Brothers' Rooms
+ // Cards 2294, 2255
+ Common::Rect u0_rect = Common::Rect(argv[0], argv[1], argv[2], argv[3]);
+ uint16 u1 = argv[3];
+ uint16 u2 = argv[2];
+
+ debugC(kDebugScript, "Opcode %d: Unknown", op);
+ debugC(kDebugScript, "u0_rect.left: %d", u0_rect.left);
+ debugC(kDebugScript, "u0_rect.top: %d", u0_rect.top);
+ debugC(kDebugScript, "u0_rect.right: %d", u0_rect.right);
+ debugC(kDebugScript, "u0_rect.bottom: %d", u0_rect.bottom);
+ debugC(kDebugScript, "u1: %d", u1);
+ debugC(kDebugScript, "u2: %d", u2);
+
+ // TODO: Fill in logic...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kDemoPreviewStack:
+ case kMystStack:
+ debugC(kDebugScript, "Opcode %d: Decrement Variable", op);
+ if (argc == 0) {
+ debugC(kDebugScript, "\tvar: %d", var);
+ uint16 varValue = _vm->_varStore->getVar(var);
+ // Logic to prevent decrement to negative
+ if (varValue != 0)
+ _vm->_varStore->setVar(var, varValue - 1);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kChannelwoodStack:
+ varUnusedCheck(op, var);
+
+ if (argc == 1) {
+ debugC(kDebugScript, "Opcode %d: Play Pipe Movie and Sound", op);
+
+ uint16 soundId = argv[0];
+ debugC(kDebugScript, "\tsoundId: %d", soundId);
+
+ _vm->_sound->playSound(soundId);
+
+ // TODO: Get Movie Location from Invoking Resource Rect, rather than
+ // hardcoded 267, 170 ?
+
+ // TODO: Need version of playMovie blocking which allows selection
+ // of start and finish points.
+ if (!_vm->_varStore->getVar(6)) {
+ // Play Pipe Extending i.e. 0 to 1/2 way through file
+ _vm->_video->playMovie(_vm->wrapMovieFilename("pipebrid", kChannelwoodStack), 267, 170);
+ } else {
+ // Play Pipe Retracting i.e. 1/2 way to end of file
+ _vm->_video->playMovie(_vm->wrapMovieFilename("pipebrid", kChannelwoodStack), 267, 170);
+ }
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kDniStack:
+ // Used in Card 5014 (Atrus)
+ // Hotspot Resource Used to hand Page to Atrus...
+ varUnusedCheck(op, var);
+ // TODO: Fill in Logic.
+ break;
+ case kDemoStack:
+ varUnusedCheck(op, var);
+
+ // Used on Card 2000, 2002 and 2003
+ // Triggered by Click
+ if (argc == 0) {
+ // TODO: Fill in Logic.. Fade in?
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_102(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kStoneshipStack:
+ varUnusedCheck(op, var);
+
+ if (argc == 2) {
+ debugC(kDebugScript, "Opcode %d: Play Book Room Movie", op);
+
+ uint16 startTime = argv[0];
+ uint16 endTime = argv[1];
+
+ debugC(kDebugScript, "\tstartTime: %d", startTime);
+ debugC(kDebugScript, "\tendTime: %d", endTime);
+
+ printf("TODO: Opcode %d Movie Time Index %d to %d\n", op, startTime, endTime);
+ // TODO: Need version of playMovie blocking which allows selection
+ // of start and finish points.
+ _vm->_video->playMovie(_vm->wrapMovieFilename("bkroom", kStoneshipStack), 159, 99);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kDemoPreviewStack:
+ case kMystStack:
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Increment Variable", op);
+ debugC(kDebugScript, "\tvar: %d", var);
+
+ // AFAIK no logic to put ceiling on increment at least in this opcode
+ _vm->_varStore->setVar(var, _vm->_varStore->getVar(var) + 1);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kChannelwoodStack:
+ varUnusedCheck(op, var);
+
+ if (argc == 2 || argc == 3) {
+ debugC(kDebugScript, "Opcode %d: Draw Full Screen Image, Optional Delay and Change Card", op);
+
+ uint16 imageId = argv[0];
+ uint16 cardId = argv[1];
+ uint16 delay = 0;
+
+ if (argc == 3)
+ delay = argv[2]; // TODO: Not sure about purpose of this parameter...
+
+ debugC(kDebugScript, "\timageId: %d", imageId);
+ debugC(kDebugScript, "\tcardId: %d", cardId);
+ debugC(kDebugScript, "\tdelay: %d", delay);
+
+ _vm->_gfx->copyImageToScreen(imageId, Common::Rect(0, 0, 544, 333));
+ _vm->_system->delayMillis(delay / 100);
+ _vm->changeToCard(cardId);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kDemoStack:
+ varUnusedCheck(op, var);
+
+ // Used on Card 2002 and 2003
+ // Triggered by Click
+ if (argc == 0) {
+ // TODO: Fill in Logic.. Fade out?
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_103(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kStoneshipStack:
+ varUnusedCheck(op, var);
+
+ if (argc == 1) {
+ // Used on Card 2197 (Sirrus' Room Drawers)
+ debugC(kDebugScript, "Opcode %d: Unknown", op);
+
+ uint16 u0 = argv[0];
+
+ debugC(kDebugScript, "\tu0: %d", u0);
+ // TODO: Fill in Logic...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kDemoPreviewStack:
+ case kMystStack:
+ // Used on Myst Card 4162 (Fireplace Grid)
+ if (argc == 1) {
+ debugC(kDebugScript, "Opcode %d: Toggle Variable with Bitmask", op);
+
+ uint16 bitmask = argv[0];
+ uint16 varValue = _vm->_varStore->getVar(var);
+
+ debugC(kDebugScript, "\tvar: %d", var);
+ debugC(kDebugScript, "\tbitmask: 0x%02X", bitmask);
+
+ if (varValue & bitmask)
+ _vm->_varStore->setVar(var, varValue & ~bitmask);
+ else
+ _vm->_varStore->setVar(var, varValue | bitmask);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_104(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kStoneshipStack:
+ varUnusedCheck(op, var);
+
+ // Used for Card 2004 (Achenar's Room Drawers)
+ // Used for Closeup of Torn Note?
+ if (argc == 1) {
+ debugC(kDebugScript, "Opcode %d: Unknown Function", op);
+
+ uint16 u0 = argv[0];
+ debugC(kDebugScript, "\tu0: %d", u0);
+
+ // TODO: Fill in Function...
+ // Does u0 correspond to a resource Id? Enable? Disable?
+ // Similar to Opcode 111 (Stoneship Version).. But does this also
+ // draw closeup image of note / change to closeup card?
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMystStack:
+ varUnusedCheck(op, var);
+
+ // Used on Myst Card 4162 and 4166 (Fireplace Puzzle Rotation Movies)
+ if (argc == 1) {
+ debugC(kDebugScript, "Opcode %d: Play Fireplace Puzzle Rotation Movies", op);
+
+ uint16 movieNum = argv[0];
+ debugC(kDebugScript, "\tmovieNum: %d", movieNum);
+
+ if (movieNum == 0)
+ _vm->_video->playMovie(_vm->wrapMovieFilename("fpin", kMystStack), 167, 5);
+ if (movieNum == 1)
+ _vm->_video->playMovie(_vm->wrapMovieFilename("fpout", kMystStack), 167, 5);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMechanicalStack:
+ varUnusedCheck(op, var);
+
+ // Used on Mechanical Card 6043 (Weapons Rack with Snake Box)
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Trigger Playing Of Snake Movie", op);
+
+ // TODO: Trigger Type 6 To Play Snake Movie.. Resource #3 on card.
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kChannelwoodStack:
+ varUnusedCheck(op, var);
+
+ // Used on Channelwood Card 3280
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Do Water Tank Valve Open Animation", op);
+ Common::Rect rect = _invokingResource->getRect();
+
+ // TODO: Need to load the image ids from Script Resources structure of VIEW
+ for (uint16 imageId = 3595; imageId <= 3601; imageId++) {
+ _vm->_gfx->copyImageToScreen(imageId, rect);
+ _vm->_system->delayMillis(50);
+ }
+
+ // TODO: Is 8 gotten from var7 of calling hotspot, rather than hardcoded?
+ _vm->_varStore->setVar(8, 1);
+ _vm->_varStore->setVar(19, 1);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_105(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ if (argc == 1) {
+ uint16 soundId = argv[0];
+
+ debugC(kDebugScript, "Opcode %d: Sound Receiver Water Button", op);
+ debugC(kDebugScript, "\tvar: %d", var);
+
+ // TODO: Complete Function including Var Change?
+ _vm->_sound->playSound(soundId);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMystStack:
+ if (argc == 1) {
+ varUnusedCheck(op, var);
+
+ uint16 soundId = argv[0];
+ uint16 boxValue = 0;
+ Audio::SoundHandle *handle;
+
+ debugC(kDebugScript, "Opcode %d: Ship Puzzle Logic", op);
+ debugC(kDebugScript, "\tsoundId: %d", soundId);
+
+ // Logic for Myst Ship Box Puzzle Solution
+ for (byte i = 0; i < 8; i++)
+ boxValue |= _vm->_varStore->getVar(i + 26) ? (1 << i) : 0;
+
+ uint16 var10 = _vm->_varStore->getVar(10);
+
+ if (boxValue == 0x32 && var10 == 0) {
+ handle = _vm->_sound->playSound(soundId);
+
+ while (_vm->_mixer->isSoundHandleActive(*handle))
+ _vm->_system->delayMillis(10);
+
+ _vm->_varStore->setVar(10, 1);
+ } else if (boxValue != 0x32 && var10 == 1) {
+ handle = _vm->_sound->playSound(soundId);
+
+ while (_vm->_mixer->isSoundHandleActive(*handle))
+ _vm->_system->delayMillis(10);
+
+ _vm->_varStore->setVar(10, 0);
+ }
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMechanicalStack:
+ varUnusedCheck(op, var);
+
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Play Stairs Movement Movie", op);
+
+ if (_vm->_varStore->getVar(10)) {
+ // TODO: Play Movie from 0 to 1/2 way...
+ _vm->_video->playMovie(_vm->wrapMovieFilename("hhstairs", kMechanicalStack), 174, 222);
+ } else {
+ // TODO: Play Movie from 1/2 way to end...
+ _vm->_video->playMovie(_vm->wrapMovieFilename("hhstairs", kMechanicalStack), 174, 222);
+ }
+ }
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_106(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ if (argc == 1) {
+ uint16 soundId = argv[0];
+
+ debugC(kDebugScript, "Opcode %d: Sound Receiver Volcanic Crack Button", op);
+ debugC(kDebugScript, "\tvar: %d", var);
+
+ // TODO: Complete Function including Var Change?
+ _vm->_sound->playSound(soundId);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_107(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ if (argc == 1) {
+ uint16 soundId = argv[0];
+
+ debugC(kDebugScript, "Opcode %d: Sound Receiver Clock Button", op);
+ debugC(kDebugScript, "\tvar: %d", var);
+
+ // TODO: Complete Function including Var Change?
+ _vm->_sound->playSound(soundId);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_108(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ if (argc == 1) {
+ uint16 soundId = argv[0];
+
+ debugC(kDebugScript, "Opcode %d: Sound Receiver Crystal Rocks Button", op);
+ debugC(kDebugScript, "\tvar: %d", var);
+
+ // TODO: Complete Function including Var Change?
+ _vm->_sound->playSound(soundId);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_109(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ if (argc == 1) {
+ uint16 soundId = argv[0];
+
+ debugC(kDebugScript, "Opcode %d: Sound Receiver Wind Button", op);
+ debugC(kDebugScript, "\tvar: %d", var);
+
+ // TODO: Complete Function including Var Change?
+ _vm->_sound->playSound(soundId);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMystStack:
+ if (argc == 1) {
+ int16 signedValue = argv[0];
+
+ debugC(kDebugScript, "Opcode %d: Add Signed Value to Var", op);
+ debugC(kDebugScript, "\tVar: %d", var);
+ debugC(kDebugScript, "\tsignedValue: %d", signedValue);
+
+ _vm->_varStore->setVar(var, _vm->_varStore->getVar(var) + signedValue);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_110(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ if (argc == 15) {
+ // Used for Selenitic Maze Runner Exit Logic
+ uint16 CardIdEntry = argv[0];
+ uint16 CardIdExit = argv[1];
+ uint16 u0 = argv[2];
+ Common::Rect rect1 = Common::Rect(argv[3], argv[4], argv[5], argv[6]);
+ uint16 rect1UpdateDirection = argv[7];
+ uint16 u1 = argv[8];
+ Common::Rect rect2 = Common::Rect(argv[9], argv[10], argv[11], argv[12]);
+ uint16 rect2UpdateDirection = argv[13];
+ uint16 u2 = argv[14];
+
+ debugC(kDebugScript, "Opcode %d: Maze Runner Exit Logic and Door Open Animation", op);
+ debugC(kDebugScript, "\tExit Card: %d", CardIdEntry);
+ debugC(kDebugScript, "\tEntry Card: %d", CardIdExit);
+ debugC(kDebugScript, "\tu0 (Exit Var?): %d", u0);
+
+ debugC(kDebugScript, "\trect1.left: %d", rect1.left);
+ debugC(kDebugScript, "\trect1.top: %d", rect1.top);
+ debugC(kDebugScript, "\trect1.right: %d", rect1.right);
+ debugC(kDebugScript, "\trect1.bottom: %d", rect1.bottom);
+ debugC(kDebugScript, "\trect1 updateDirection: %d", rect1UpdateDirection);
+ debugC(kDebugScript, "\tu1: %d", u1);
+
+ debugC(kDebugScript, "\trect2.left: %d", rect2.left);
+ debugC(kDebugScript, "\trect2.top: %d", rect2.top);
+ debugC(kDebugScript, "\trect2.right: %d", rect2.right);
+ debugC(kDebugScript, "\trect2.bottom: %d", rect2.bottom);
+ debugC(kDebugScript, "\trect2 updateDirection: %d", rect2UpdateDirection);
+ debugC(kDebugScript, "\tu2: %d", u2);
+
+ // TODO: Finish Implementing Logic...
+ // HACK: Bypass Higher Logic for now...
+ _vm->changeToCard(argv[1]);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_111(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ if (argc == 0) {
+ // Used on Card 1245 (Sound Receiver)
+ // Used by Source Selection Buttons...
+
+ debugC(kDebugScript, "Opcode %d: Unknown", op);
+ // TODO: Fill in Function...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kStoneshipStack:
+ if (argc == 1) {
+ // Used for Card 2004 (Achenar's Room Drawers)
+ // Used by Drawers Hotspots...
+
+ debugC(kDebugScript, "Opcode %d: Unknown Function", op);
+
+ uint16 u0 = argv[0];
+ debugC(kDebugScript, "\tu0: %d", u0);
+
+ // TODO: Fill in Function...
+ // Does u0 correspond to a resource Id? Enable? Disable?
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_112(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kStoneshipStack:
+ // Used for Card 2013 (Achenar's Rose-Skull Hologram)
+ if (argc == 3) {
+ debugC(kDebugScript, "Opcode %d: Rose-Skull Hologram Playback", op);
+
+ uint16 varValue = _vm->_varStore->getVar(var);
+
+ debugC(kDebugScript, "\tVar: %d = %d", var, varValue);
+
+ uint16 startPoint = argv[0];
+ uint16 endPoint = argv[1];
+ uint16 u0 = argv[2];
+
+ debugC(kDebugScript, "\tstartPoint: %d", startPoint);
+ debugC(kDebugScript, "\tendPoint: %d", endPoint);
+ debugC(kDebugScript, "\tu0: %d", u0);
+
+ // TODO: Fill in Function...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_113(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used on Myst 4143 (Dock near Marker Switch)
+ if (argc == 9) {
+ uint16 soundId = argv[0];
+
+ uint16 u0 = argv[1];
+ uint16 u1 = argv[2];
+
+ Common::Rect rect = Common::Rect(argv[3], argv[4], argv[5], argv[6]);
+
+ uint16 updateDirection = argv[7];
+ uint16 u2 = argv[8];
+
+ debugC(kDebugScript, "Opcode %d: Vault Open Logic", op);
+ debugC(kDebugScript, "\tsoundId: %d", soundId);
+ debugC(kDebugScript, "\tu0: %d", u0);
+ debugC(kDebugScript, "\tu1: %d", u1);
+
+ debugC(kDebugScript, "\trect.left: %d", rect.left);
+ debugC(kDebugScript, "\trect.top: %d", rect.top);
+ debugC(kDebugScript, "\trect.right: %d", rect.right);
+ debugC(kDebugScript, "\trect.bottom: %d", rect.bottom);
+ debugC(kDebugScript, "\trect updateDirection: %d", updateDirection);
+ debugC(kDebugScript, "\tu2: %d", u2);
+
+ if ((_vm->_varStore->getVar(2) == 1) &&
+ (_vm->_varStore->getVar(3) == 1) &&
+ (_vm->_varStore->getVar(4) == 0) &&
+ (_vm->_varStore->getVar(5) == 1) &&
+ (_vm->_varStore->getVar(6) == 1) &&
+ (_vm->_varStore->getVar(7) == 1) &&
+ (_vm->_varStore->getVar(8) == 1) &&
+ (_vm->_varStore->getVar(9) == 1)) {
+ // TODO: Implement correct function...
+ // Blit Image in Left to Right Vertical stripes i.e. transistion
+ // like door opening
+ _vm->_sound->playSound(soundId);
+ // TODO: Set 41 to 1 if page already present in hand.
+ _vm->_varStore->setVar(41, 2);
+ }
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_114(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used on Myst 4143 (Dock near Marker Switch)
+ if (argc == 9) {
+ uint16 soundId = argv[0];
+
+ uint16 u0 = argv[1];
+ uint16 u1 = argv[2];
+
+ Common::Rect rect = Common::Rect(argv[3], argv[4], argv[5], argv[6]);
+
+ uint16 updateDirection = argv[7];
+ uint16 u2 = argv[8];
+
+ debugC(kDebugScript, "Opcode %d: Vault Close Logic", op);
+ debugC(kDebugScript, "\tsoundId: %d", soundId);
+ debugC(kDebugScript, "\tu0: %d", u0);
+ debugC(kDebugScript, "\tu1: %d", u1);
+
+ debugC(kDebugScript, "\trect.left: %d", rect.left);
+ debugC(kDebugScript, "\trect.top: %d", rect.top);
+ debugC(kDebugScript, "\trect.right: %d", rect.right);
+ debugC(kDebugScript, "\trect.bottom: %d", rect.bottom);
+ debugC(kDebugScript, "\tupdateDirection: %d", updateDirection);
+ debugC(kDebugScript, "\tu2: %d", u2);
+
+ if ((_vm->_varStore->getVar(2) == 1) &&
+ (_vm->_varStore->getVar(3) == 1) &&
+ (_vm->_varStore->getVar(4) == 1) &&
+ (_vm->_varStore->getVar(5) == 1) &&
+ (_vm->_varStore->getVar(6) == 1) &&
+ (_vm->_varStore->getVar(7) == 1) &&
+ (_vm->_varStore->getVar(8) == 1) &&
+ (_vm->_varStore->getVar(9) == 1)) {
+ // TODO: Implement correct function...
+ // Blit Image in Right to Left Vertical stripes i.e. transistion
+ // like door closing
+ _vm->_sound->playSound(soundId);
+ _vm->_varStore->setVar(41, 0);
+ }
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_115(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ varUnusedCheck(op, var);
+
+ if (argc == 11) {
+ // Used for Selenitic Card 1147 (Musical Door Lock Button)
+ uint16 imageIdClose = argv[0]; // TODO: Sound Id?
+ uint16 imageIdOpen = argv[1]; // TODO: Sound Id?
+
+ uint16 cardIdOpen = argv[2];
+
+ uint16 u0 = argv[3];
+ uint16 u1 = argv[4];
+
+ Common::Rect rect = Common::Rect(argv[5], argv[6], argv[7], argv[8]);
+
+ uint16 updateDirection = argv[9];
+ uint16 u2 = argv[10];
+
+ debugC(kDebugScript, "Music Door Lock Logic...");
+ debugC(kDebugScript, "\timageId (Close): %d", imageIdClose);
+ debugC(kDebugScript, "\timageId (Open): %d", imageIdOpen);
+ debugC(kDebugScript, "\tcardId (Open): %d", cardIdOpen);
+ debugC(kDebugScript, "\tu0: %d", u0);
+ debugC(kDebugScript, "\tu1: %d", u1);
+
+ debugC(kDebugScript, "\trect.left: %d", rect.left);
+ debugC(kDebugScript, "\trect.top: %d", rect.top);
+ debugC(kDebugScript, "\trect.right: %d", rect.right);
+ debugC(kDebugScript, "\trect.bottom: %d", rect.bottom);
+ debugC(kDebugScript, "\trect updateDirection: %d", updateDirection);
+ debugC(kDebugScript, "\tu2: %d", u2);
+
+ // TODO: Fix Logic...
+ // HACK: Bypass Door Lock For Now
+ _vm->changeToCard(cardIdOpen);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kDemoPreviewStack:
+ case kMystStack:
+ if (argc == 3) {
+ uint16 cardIdLose = argv[0];
+ uint16 cardIdBookCover = argv[1];
+ uint16 soundIdAddPage = argv[2];
+
+ debugC(kDebugScript, "Opcode %d: Red and Blue Book/Page Interaction", op);
+ debugC(kDebugScript, "Var: %d", var);
+ debugC(kDebugScript, "Card Id (Lose): %d", cardIdLose);
+ debugC(kDebugScript, "Card Id (Book Cover): %d", cardIdBookCover);
+ debugC(kDebugScript, "SoundId (Add Page): %d", soundIdAddPage);
+
+ // TODO: if holding page for this book, play SoundIdAddPage
+ if (false) { // TODO: Should be access to mainCursor...
+ _vm->_sound->playSound(soundIdAddPage);
+ // TODO: Code for updating variables based on adding page
+ }
+
+ // TODO: Add Tweak to improve original logic by denying
+ // lose until all red / blue pages collected, rather
+ // than allowing shortcut based on 1 fireplace page?
+
+ // If holding last page for this book i.e. var 24/25
+ // Then trigger Trapped in Book Losing Ending
+ if ((var == 100 && !_vm->_varStore->getVar(25)) ||
+ (var == 101 && !_vm->_varStore->getVar(24))) {
+ // TODO: Clear mainCursor back to nominal..
+ _vm->changeToCard(cardIdLose);
+ } else
+ _vm->changeToCard(cardIdBookCover);
+
+ // TODO: Is this logic here?
+ // i.e. If was holding page, wait then auto open and play book...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ varUnusedCheck(op, var);
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_116(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kStoneshipStack:
+ varUnusedCheck(op, var);
+
+ if (argc == 1) {
+ // Used on Card 2111 (Compass Rose)
+ // Called when Button Clicked.
+ uint16 correctButton = argv[0];
+
+ if (correctButton) {
+ // Correct Button -> Light On Logic
+ // TODO: Deal with if main power on?
+ _vm->_varStore->setVar(16, 1);
+ _vm->_varStore->setVar(30, 0);
+ } else {
+ // Wrong Button -> Power Failure Logic
+ // TODO: Fill in Alarm
+ _vm->_varStore->setVar(16, 0);
+ _vm->_varStore->setVar(30, 2);
+ _vm->_varStore->setVar(33, 0);
+ }
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMystStack:
+ if (argc == 1) {
+ // Used on Card 4006 (Clock Tower Time Controls)
+ uint16 soundId = argv[0];
+
+ debugC(kDebugScript, "Opcode %d: Clock Tower Bridge Puzzle Execute Button", op);
+
+ uint16 bridgeState = _vm->_varStore->getVar(12);
+ uint16 currentTime = _vm->_varStore->getVar(43);
+
+ const uint16 correctTime = 32; // 2:40 i.e. From 12 Noon in 5 min increments
+
+ if (!bridgeState && currentTime == correctTime) {
+ _vm->_sound->playSound(soundId);
+
+ // TODO: Play only 1st half of movie i.e. gears rise up
+ _vm->_video->playMovie(_vm->wrapMovieFilename("gears", kMystStack), 305, 36);
+
+ bridgeState = 1;
+ _vm->_varStore->setVar(12, bridgeState);
+ } else if (bridgeState && currentTime != correctTime) {
+ _vm->_sound->playSound(soundId);
+
+ // TODO: Play only 2nd half of movie i.e. gears sink down
+ _vm->_video->playMovie(_vm->wrapMovieFilename("gears", kMystStack), 305, 36);
+
+ bridgeState = 0;
+ _vm->_varStore->setVar(12, bridgeState);
+ }
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_117(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kStoneshipStack:
+ varUnusedCheck(op, var);
+
+ if (argc == 0) {
+ // Used on Card 2132 (Chest at Bottom of Lighthouse)
+ // Called when Valve Hotspot Clicked.
+ // TODO: Fill in Function to play right section of movie
+ // based on valve state and water in chest..
+ _vm->_video->playMovie(_vm->wrapMovieFilename("ligspig", kStoneshipStack), 97, 267);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMystStack:
+ if (argc == 2) {
+ // Used by Myst Imager Control Button
+ uint16 varValue = _vm->_varStore->getVar(var);
+
+ if (varValue)
+ _vm->_sound->playSound(argv[1]);
+ else
+ _vm->_sound->playSound(argv[0]);
+
+ _vm->_varStore->setVar(var, !varValue);
+ // TODO: Change Var 45 "Dock Forechamber Imager Water Effect Enabled" here?
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kChannelwoodStack:
+ varUnusedCheck(op, var);
+
+ if (argc == 1) {
+ // Used on Card 3012 (Temple Hologram Monitor)
+ uint16 button = argv[0]; // 0 to 3
+ _vm->_varStore->setVar(17, button);
+ switch (button) {
+ case 0:
+ _vm->_video->playMovie(_vm->wrapMovieFilename("monalgh", kChannelwoodStack), 227, 71);
+ break;
+ case 1:
+ _vm->_video->playMovie(_vm->wrapMovieFilename("monamth", kChannelwoodStack), 227, 71);
+ break;
+ case 2:
+ _vm->_video->playMovie(_vm->wrapMovieFilename("monasirs", kChannelwoodStack), 227, 71);
+ break;
+ case 3:
+ _vm->_video->playMovie(_vm->wrapMovieFilename("monsmsg", kChannelwoodStack), 227, 71);
+ break;
+ default:
+ warning("Opcode %d Control Variable Out of Range", op);
+ break;
+ }
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_118(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kStoneshipStack:
+ if (argc == 0) {
+ // Used on Card 2126 (Lighthouse Looking Along Plank)
+ // Called when Exit Resource is clicked
+
+ // TODO: Implement Function...
+ // If holding Key to Lamp Room Trapdoor, drop to bottom of
+ // Lighthouse...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMystStack:
+ varUnusedCheck(op, var);
+
+ if (argc == 5) {
+ // Used by Card 4709 (Myst Imager Control Panel Red Button)
+
+ debugC(kDebugScript, "Opcode %d: Imager Change Value", op);
+
+ uint16 soundIdBeepLo = argv[0];
+ uint16 soundIdBeepHi = argv[1];
+ uint16 soundIdBwapp = argv[2];
+ uint16 soundIdBeepTune = argv[3]; // 5 tones..
+ uint16 soundIdPanelSlam = argv[4];
+
+ debugC(kDebugScript, "\tsoundIdBeepLo: %d", soundIdBeepLo);
+ debugC(kDebugScript, "\tsoundIdBeepHi: %d", soundIdBeepHi);
+ debugC(kDebugScript, "\tsoundIdBwapp: %d", soundIdBwapp);
+ debugC(kDebugScript, "\tsoundIdBeepTune: %d", soundIdBeepTune);
+ debugC(kDebugScript, "\tsoundIdPanelSlam: %d", soundIdPanelSlam);
+
+ _vm->_sound->playSound(soundIdBeepLo);
+
+ // TODO: Complete Logic...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kChannelwoodStack:
+ varUnusedCheck(op, var);
+
+ if (argc == 0) {
+ // Used on Card 3318 (Sirrus' Room Nightstand Drawer)
+ // Triggered when clicked on drawer
+ // TODO: Implement function...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_119(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kStoneshipStack:
+ if (argc == 0) {
+ // Used on Card 2143 (Lighthouse Trapdoor)
+ // Called when Lock Hotspot Clicked while holding key.
+ _vm->_video->playMovie(_vm->wrapMovieFilename("openloc", kStoneshipStack), 187, 72);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMystStack:
+ if (argc == 1) {
+ // Used on Card 4383 and 4451 (Tower Elevator)
+ switch (argv[0]) {
+ case 0:
+ _vm->_video->playMovie(_vm->wrapMovieFilename("libdown", kMystStack), 216, 78);
+ break;
+ case 1:
+ _vm->_video->playMovie(_vm->wrapMovieFilename("libup", kMystStack), 214, 75);
+ break;
+ default:
+ break;
+ }
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kChannelwoodStack:
+ if (argc == 0) {
+ // Used on Card 3333 (Temple Hologram)
+ // TODO: Not 100% sure about movie position...
+ switch (_vm->_varStore->getVar(17)) {
+ case 0:
+ _vm->_video->playMovie(_vm->wrapMovieFilename("holoalgh", kChannelwoodStack), 126, 74);
+ break;
+ case 1:
+ _vm->_video->playMovie(_vm->wrapMovieFilename("holoamth", kChannelwoodStack), 126, 74);
+ break;
+ case 2:
+ _vm->_video->playMovie(_vm->wrapMovieFilename("holoasir", kChannelwoodStack), 126, 74);
+ break;
+ case 3:
+ _vm->_video->playMovie(_vm->wrapMovieFilename("holosmsg", kChannelwoodStack), 126, 74);
+ break;
+ default:
+ warning("Opcode %d Control Variable Out of Range", op);
+ break;
+ }
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_120(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ MystResource *_top;
+
+ switch (_vm->getCurStack()) {
+ case kStoneshipStack:
+ if (argc == 1) {
+ // Used for Cards 2285, 2289, 2247, 2251 (Side Doors in Tunnels Down To Brothers Rooms)
+ uint16 movieId = argv[0];
+
+ debugC(kDebugScript, "Opcode %d: Play Side Door Movies", op);
+ debugC(kDebugScript, "\tmovieId: %d", movieId);
+
+ switch (movieId) {
+ case 0:
+ // Card 2251
+ _vm->_video->playMovie(_vm->wrapMovieFilename("tunaup", kStoneshipStack), 149, 161);
+ break;
+ case 1:
+ // Card 2247
+ _vm->_video->playMovie(_vm->wrapMovieFilename("tunadown", kStoneshipStack), 218, 150);
+ break;
+ case 2:
+ // Card 2289
+ _vm->_video->playMovie(_vm->wrapMovieFilename("tuncup", kStoneshipStack), 259, 161);
+ break;
+ case 3:
+ // Card 2285
+ _vm->_video->playMovie(_vm->wrapMovieFilename("tuncdown", kStoneshipStack), 166, 150);
+ break;
+ default:
+ warning("Opcode 120 MovieId Out Of Range");
+ break;
+ }
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMystStack:
+ // Used for Card 4297 (Generator Puzzle Buttons)
+ debugC(kDebugScript, "Opcode %d: Toggle Var8 of Invoking Resource", op);
+ _top = _invokingResource;
+
+ while(_top->_parent != NULL)
+ _top = _top->_parent;
+
+ if (argc == 0) {
+ uint16 var8 = _top->getType8Var();
+ if (var8 != 0xFFFF)
+ _vm->_varStore->setVar(var8, !_vm->_varStore->getVar(var8));
+ else
+ warning("Opcode 120: No invoking Resource Var 8 found!");
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_121(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used on Card 4100 (Cabin Safe Buttons)
+ // Correct Solution (724) -> Var 67=2, 68=7, 69=5
+ // Jump to Card 4103 when solution correct and handle pulled...
+ if (argc == 0) {
+ uint16 varValue = _vm->_varStore->getVar(var);
+ if (varValue == 0)
+ varValue = 9;
+ else
+ varValue--;
+ _vm->_varStore->setVar(var, varValue);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMechanicalStack:
+ varUnusedCheck(op, var);
+
+ if (argc == 2) {
+ uint16 startTime = argv[0];
+ uint16 endTime = argv[1];
+
+ printf("TODO: Opcode %d Movie Time Index %d to %d\n", op, startTime, endTime);
+ // TODO: Need version of playMovie blocking which allows selection
+ // of start and finish points.
+ _vm->_video->playMovie(_vm->wrapMovieFilename("ewindow", kMechanicalStack), 253, 0);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ varUnusedCheck(op, var);
+
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_122(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kChannelwoodStack:
+ // Used on Channelwood Card 3280
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Do Water Tank Valve Close Animation", op);
+ Common::Rect rect = _invokingResource->getRect();
+
+ // TODO: Need to load the image ids from Script Resources structure of VIEW
+ for (uint16 imageId = 3601; imageId >= 3595; imageId--) {
+ _vm->_gfx->copyImageToScreen(imageId, rect);
+ _vm->_system->delayMillis(50);
+ }
+
+ // TODO: Is 8 gotten from var7 of calling hotspot, rather than hard-coded?
+ _vm->_varStore->setVar(8, 0);
+ _vm->_varStore->setVar(19, 0);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMechanicalStack:
+ if (argc == 0) {
+ // Used on Card 6120 (Elevator)
+ // Called when Exit Midde Button Pressed
+
+ // TODO: hcelev? Movie of Elevator?
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_123(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMechanicalStack:
+ if (argc == 2) {
+ // Used on Card 6154
+ uint16 start_time = argv[0];
+ uint16 end_time = argv[1];
+
+ printf("TODO: Opcode %d Movie Time Index %d to %d\n", op, start_time, end_time);
+ // TODO: Need version of playMovie blocking which allows selection
+ // of start and finish points.
+ // TODO: Not 100% sure about movie position
+ _vm->_video->playMovie(_vm->wrapMovieFilename("hcelev", kMechanicalStack), 205, 40);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_124(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMechanicalStack:
+ if (argc == 0) {
+ // Used by Card 6156 (Fortress Rotation Controls)
+ // Called when Red Exit Button Pressed to raise Elevator
+
+ // TODO: Fill in Code...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_125(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kStoneshipStack:
+ if (argc == 1) {
+ // Used on Card 2197 (Sirrus' Room Drawers)
+ debugC(kDebugScript, "Opcode %d: Unknown uses Var %d", op, var);
+
+ uint16 u0 = argv[0];
+
+ debugC(kDebugScript, "\tu0: %d", u0);
+ // TODO: Fill in Logic...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMechanicalStack:
+ if (argc == 0) {
+ // Used on Card 6267 (Code Lock)
+ // Called by Red Execute Button...
+ debugC(kDebugScript, "Opcode %d: Code Lock Execute...", op);
+
+ // TODO: Fill in Logic For Code Lock...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_126(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMechanicalStack:
+ if (argc == 0) {
+ // Used by Card 6120 (Fortress Elevator)
+ // Called when Red Exit Button Pressed to raise Elevator and
+ // exit is clicked...
+
+ // TODO: Fill in Code...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_127(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kChannelwoodStack:
+ if (argc == 2) {
+ // Used by Card 3262 (Elevator)
+ debugC(kDebugScript, "Opcode %d: Unknown...", op);
+
+ uint16 u0 = argv[0];
+ uint16 u1 = argv[1];
+
+ debugC(kDebugScript, "\tu0: %d", u0);
+ debugC(kDebugScript, "\tu1: %d", u1);
+
+ // TODO: Fill in Code...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMechanicalStack:
+ if (argc == 0) {
+ // Used for Mech Card 6226 (3 Crystals)
+ _vm->_varStore->setVar(20, 1);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_128(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMechanicalStack:
+ if (argc == 0) {
+ // Used for Mech Card 6226 (3 Crystals)
+ _vm->_varStore->setVar(20, 0);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_129(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kChannelwoodStack:
+ if (argc == 1) {
+ // Used by Card 3262 (Elevator)
+ debugC(kDebugScript, "Opcode %d: Unknown...", op);
+
+ uint16 cardId = argv[0];
+
+ debugC(kDebugScript, "\tcardId: %d", cardId);
+
+ // TODO: Fill in Code...
+ _vm->changeToCard(cardId);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMechanicalStack:
+ if (argc == 0) {
+ // Used for Mech Card 6226 (3 Crystals)
+ _vm->_varStore->setVar(21, 1);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_130(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMechanicalStack:
+ if (argc == 0) {
+ // Used for Mech Card 6226 (3 Crystals)
+ _vm->_varStore->setVar(21, 0);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_131(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMechanicalStack:
+ if (argc == 0) {
+ // Used for Mech Card 6226 (3 Crystals)
+ _vm->_varStore->setVar(22, 1);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_132(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMechanicalStack:
+ if (argc == 0) {
+ // Used for Mech Card 6226 (3 Crystals)
+ _vm->_varStore->setVar(22, 0);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_133(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used on Card 4500 (Stellar Observatory Controls)
+ if (argc == 1) {
+ // Called by Telescope Slew Button
+ uint16 soundId = argv[0];
+
+ // TODO: Function to change variables controlling telescope view
+ // etc.
+
+ // TODO: Sound seems to be stuck looping?
+ _vm->_sound->playSound(soundId);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_147(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ if (argc == 0) {
+ // TODO: Extra Logic to do this in INIT process watching cursor and var 98?
+ _vm->_varStore->setVar(98, 0);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_164(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used on Card 4530 (Rocketship Music Slider Controls)
+ // TODO: Finish Implementation...
+ // Var 105 is used to set between 0 to 2 = No Function, Movie Playback and Linkable...
+ // This is called when Var 105 = 1 i.e. this plays back Movie...
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_169(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used on Card 4099 (In Cabin, Looking Out Door)
+ // TODO: Finish Implementation...
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_181(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ if (argc == 0) {
+ // TODO: Logic for lighting the match
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_182(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ if (argc == 0) {
+ // TODO: Logic for lighting the match
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_183(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ if (argc == 0) {
+ // Used for Myst Cards 4257, 4260, 4263, 4266, 4269, 4272, 4275 and 4278 (Ship Puzzle Boxes)
+ _vm->_varStore->setVar(105, 1);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_184(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ if (argc == 0) {
+ // Used for Myst Cards 4257, 4260, 4263, 4266, 4269, 4272, 4275 and 4278 (Ship Puzzle Boxes)
+ _vm->_varStore->setVar(105, 0);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_185(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ if (argc == 0) {
+ // Used for Myst Card 4098 (Cabin Boiler Pilot Light)
+ // TODO: Extra Logic to do this in INIT process watching cursor and var 98?
+ _vm->_varStore->setVar(98, 1);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_196(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kDemoPreviewStack:
+ // Used on Card ...
+ // TODO: Finish Implementation...
+ // Voice Over and Card Advance?
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_197(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kDemoPreviewStack:
+ // Used on Card ...
+ // TODO: Finish Implementation...
+ // Voice Over and Card Advance?
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+// TODO: Merge with Opcode 42?
+void MystScriptParser::opcode_198(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kDemoPreviewStack:
+ if (argc == 0) {
+ // Nuh-uh! No leaving the library in the demo!
+ GUI::MessageDialog dialog("You can't leave the library in the demo.");
+ dialog.runModal();
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMystStack:
+ // Used on Card 4143 (Dock near Marker Switch, facing Cogs)
+ if (argc == 9) {
+ uint16 soundId = argv[0];
+ uint16 u0 = argv[1];
+ uint16 u1 = argv[2];
+ Common::Rect rect = Common::Rect(argv[3], argv[4], argv[5], argv[6]);
+ uint16 updateDirection = argv[7];
+ uint16 u2 = argv[8];
+
+ debugC(kDebugScript, "Opcode %d: Close Dock Marker Switch Vault", op);
+ debugC(kDebugScript, "\tsoundId: %d", soundId);
+ debugC(kDebugScript, "\tu0: %d", u0);
+ debugC(kDebugScript, "\tu1: %d", u1);
+
+ debugC(kDebugScript, "\trect.left: %d", rect.left);
+ debugC(kDebugScript, "\trect.top: %d", rect.top);
+ debugC(kDebugScript, "\trect.right: %d", rect.right);
+ debugC(kDebugScript, "\trect.bottom: %d", rect.bottom);
+ debugC(kDebugScript, "\tupdateDirection: %d", updateDirection);
+ debugC(kDebugScript, "\tu2: %d", u2);
+
+ Audio::SoundHandle *handle;
+ if (_vm->_varStore->getVar(41) != 0) {
+ handle = _vm->_sound->playSound(soundId);
+
+ while (_vm->_mixer->isSoundHandleActive(*handle))
+ _vm->_system->delayMillis(10);
+ // TODO: Do Image Blit
+ }
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_199(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Myst Imager Control Execute Button Logic", op);
+
+ uint16 numericSelection = (_vm->_varStore->getVar(36) + 1) % 10;
+ numericSelection += ((_vm->_varStore->getVar(35) + 1) % 10) * 10;
+
+ debugC(kDebugScript, "\tImager Selection: %d", numericSelection);
+
+ switch (numericSelection) {
+ case 40:
+ _vm->_varStore->setVar(51, 1); // Mountain
+ break;
+ case 67:
+ _vm->_varStore->setVar(51, 2); // Water
+ break;
+ case 47:
+ _vm->_varStore->setVar(51, 4); // Marker Switch
+ break;
+ case 8:
+ _vm->_varStore->setVar(51, 3); // Atrus
+ break;
+ default:
+ _vm->_varStore->setVar(51, 0); // Blank
+ break;
+ }
+
+ // TODO: Fill in Logic
+ //{ 34, 2, "Dock Forechamber Imager State" }, // 0 to 2 = Off, Mountain, Water
+ //{ 310, 0, "Dock Forechamber Imager Control Temp Value?" }
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kDemoPreviewStack:
+ // Used on Card ...
+ // TODO: Finish Implementation...
+ // Voice Over and Card Advance?
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+// Selenitic Stack Movies For Maze Runner (Card 1191)
+static const char* kHCMovPathSelenitic[36] = {
+ "backa1",
+ "backe1",
+ "backf0",
+ "backf1",
+ "backl0",
+ "backl1",
+ "backo0",
+ "backo1",
+ "backp0",
+ "backp1",
+ "backr0",
+ "backr1",
+ "backs0",
+ "backs1",
+ "forwa1",
+ "forwe0",
+ "forwf0",
+ "forwf1",
+ "forwl0",
+ "forwl1",
+ "forwo0",
+ "forwo1",
+ "forwp0",
+ "forwp1",
+ "forwr0",
+ "forwr1",
+ "forws0",
+ "forws1",
+ "left00",
+ "left01",
+ "left10",
+ "left11",
+ "right00",
+ "right01",
+ "right10",
+ "right11"
+};
+
+static struct {
+ bool enabled;
+
+ uint16 var;
+ uint16 imageCount;
+ uint16 imageBaseId;
+ uint16 soundDecrement;
+ uint16 soundIncrement;
+
+ // Myst Demo slideshow variables
+ uint16 cardId;
+ uint32 lastCardTime;
+} g_opcode200Parameters;
+
+void MystScriptParser::opcode_200_run() {
+ static uint16 lastImageIndex = 0;
+ uint16 curImageIndex;
+ Common::Rect rect;
+
+ if (g_opcode200Parameters.enabled) {
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ // Used on Card 1191 (Maze Runner)
+
+ // TODO: Implementation Movie Function..
+ if (false) {
+ _vm->_video->playMovie(_vm->wrapMovieFilename(kHCMovPathSelenitic[0], kSeleniticStack), 201, 26);
+ }
+ break;
+ case kStoneshipStack:
+ // Used for Card 2013 (Achenar's Rose-Skull Hologram)
+
+ // TODO: Implement Function...
+ break;
+ case kDemoPreviewStack:
+ case kMystStack:
+ curImageIndex = _vm->_varStore->getVar(g_opcode200Parameters.var);
+
+ if (curImageIndex >= g_opcode200Parameters.imageCount) {
+ curImageIndex = g_opcode200Parameters.imageCount - 1;
+ _vm->_varStore->setVar(g_opcode200Parameters.var, curImageIndex);
+ }
+
+ // HACK: Think these images are centered on screen (when smaller than full screen),
+ // and since no _gfx call for image size, hack this to deal with this case for now...
+ if (_vm->getCurCard() == 4059)
+ rect = Common::Rect(157, 115, 544, 333);
+ else
+ rect = Common::Rect(0, 0, 544, 333);
+
+ _vm->_gfx->copyImageToScreen(g_opcode200Parameters.imageBaseId + curImageIndex, rect);
+
+ // TODO: Comparison with original engine shows that this simple solution
+ // may not be the correct one and the choice of which sound
+ // may be more complicated or even random..
+ if (curImageIndex < lastImageIndex && g_opcode200Parameters.soundDecrement != 0)
+ _vm->_sound->playSound(g_opcode200Parameters.soundDecrement);
+ else if (curImageIndex > lastImageIndex && g_opcode200Parameters.soundIncrement != 0)
+ _vm->_sound->playSound(g_opcode200Parameters.soundIncrement);
+
+ lastImageIndex = curImageIndex;
+ break;
+ case kCreditsStack:
+ curImageIndex = _vm->_varStore->getVar(g_opcode200Parameters.var);
+
+ if (_vm->_system->getMillis() - g_opcode200Parameters.lastCardTime >= 7 * 1000) {
+ // After the 6th image has shown, it's time to quit
+ if (curImageIndex == 7)
+ _vm->_system->quit();
+
+ // Note: The modulus by 6 is because the 6th image is the one at imageBaseId
+ _vm->_gfx->copyImageToScreen(g_opcode200Parameters.imageBaseId + curImageIndex % 6, Common::Rect(0, 0, 544, 333));
+
+ _vm->_varStore->setVar(g_opcode200Parameters.var, curImageIndex + 1);
+ g_opcode200Parameters.lastCardTime = _vm->_system->getMillis();
+ }
+ break;
+ case kMechanicalStack:
+ // Used on Card 6238 (Sirrus' Throne) and Card 6027 (Achenar's Throne)
+ // g_opcode200Parameters.var == 0 for Achenar
+ // g_opcode200Parameters.var == 1 for Sirrus
+
+ // TODO: Fill in Function...
+ // Variable indicates that this is related to Secret Panel State
+ break;
+ case kDemoStack:
+ // Used on Card 2000
+
+ // TODO: Fill in Function...
+ break;
+ case kDemoSlidesStack:
+ // Used on Cards...
+ if (_vm->_system->getMillis() - g_opcode200Parameters.lastCardTime >= 2 * 1000)
+ _vm->changeToCard(g_opcode200Parameters.cardId);
+ break;
+ }
+ }
+}
+
+void MystScriptParser::opcode_200_disable() {
+ g_opcode200Parameters.enabled = false;
+ g_opcode200Parameters.var = 0;
+ g_opcode200Parameters.imageCount = 0;
+ g_opcode200Parameters.imageBaseId = 0;
+ g_opcode200Parameters.soundDecrement = 0;
+ g_opcode200Parameters.soundIncrement = 0;
+}
+
+void MystScriptParser::opcode_200(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kIntroStack:
+ varUnusedCheck(op, var);
+
+ // TODO: Play Intro Movies..
+ // and then _vm->changeToCard(2);
+ unknown(op, var, argc, argv);
+ break;
+ case kSeleniticStack:
+ varUnusedCheck(op, var);
+
+ // Used for Card 1191 (Maze Runner)
+ if (argc == 0) {
+ g_opcode200Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kStoneshipStack:
+ varUnusedCheck(op, var);
+
+ // Used for Card 2013 (Achenar's Rose-Skull Hologram)
+ if (argc == 0) {
+ g_opcode200Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kDemoPreviewStack:
+ case kMystStack:
+ if (argc == 4) {
+ g_opcode200Parameters.var = var;
+ g_opcode200Parameters.imageCount = argv[0];
+ g_opcode200Parameters.imageBaseId = argv[1];
+ g_opcode200Parameters.soundDecrement = argv[2];
+ g_opcode200Parameters.soundIncrement = argv[3];
+ g_opcode200Parameters.enabled = true;
+
+ _vm->_varStore->setVar(var, 0);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMechanicalStack:
+ // Used on Card 6238 (Sirrus' Throne) and Card 6027 (Achenar's Throne)
+ if (argc == 0) {
+ g_opcode200Parameters.var = var;
+ g_opcode200Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kDniStack:
+ varUnusedCheck(op, var);
+ // Used on Card 5014
+
+ // TODO: Logic for Atrus Reactions and Movies
+ if (false) {
+ // Var 0 used for Atrus Gone (from across room) 0 = Present, 1 = Not Present
+ // Var 1 used for Myst Book Status 0 = Not Usuable
+ // 1 = Openable, but not linkable (Atrus Gone?)
+ // 2 = Linkable
+ // Var 2 used for Music Type 0 to 2..
+ // Var 106 used for Atrus Static Image State 0 = Initial State
+ // 1 = Holding Out Hand for Page
+ // 2 = Gone, Book Open
+ // 3 = Back #1
+ // 4 = Back #2
+ _vm->_video->playMovie(_vm->wrapMovieFilename("atr1nopg", kDniStack), 215, 77);
+ _vm->_video->playMovie(_vm->wrapMovieFilename("atr1page", kDniStack), 215, 77);
+ _vm->_video->playMovie(_vm->wrapMovieFilename("atrus2", kDniStack), 215, 77);
+ _vm->_video->playMovie(_vm->wrapMovieFilename("atrwrite", kDniStack), 215, 77);
+ }
+ break;
+ case kCreditsStack:
+ if (argc == 0) {
+ g_opcode200Parameters.var = var;
+ // TODO: Pass ImageCount, rather than hardcoded in run process?
+ g_opcode200Parameters.imageBaseId = _vm->getCurCard();
+ g_opcode200Parameters.lastCardTime = _vm->_system->getMillis();
+ g_opcode200Parameters.enabled = true;
+
+ _vm->_varStore->setVar(var, 1);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kDemoStack:
+ // Used on Card 2000
+ if (argc == 0) {
+ g_opcode200Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kDemoSlidesStack:
+ // Used on Cards...
+ if (argc == 1) {
+ g_opcode200Parameters.cardId = argv[0];
+ g_opcode200Parameters.lastCardTime = _vm->_system->getMillis();
+ g_opcode200Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+static struct {
+ uint16 u0;
+ uint16 u1;
+ uint16 u2;
+
+ uint16 lastVar105;
+ uint16 soundId;
+
+ bool enabled;
+} g_opcode201Parameters;
+
+void MystScriptParser::opcode_201_run() {
+ uint16 var105;
+
+ if (g_opcode201Parameters.enabled) {
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ // Used for Card 1191 (Maze Runner)
+
+ // TODO: Fill in Function...
+ break;
+ case kStoneshipStack:
+ // Used for Card 2013 (Achenar's Rose-Skull Hologram)
+
+ // TODO: Fill in Function...
+ break;
+ case kMystStack:
+ var105 = _vm->_varStore->getVar(105);
+ if (var105 && !g_opcode201Parameters.lastVar105)
+ _vm->_sound->playSound(g_opcode201Parameters.soundId);
+ g_opcode201Parameters.lastVar105 = var105;
+ break;
+ case kMechanicalStack:
+ // Used for Card 6159 (Facing Corridor to Fortress Elevator)
+
+ // g_opcode201Parameters.u0
+ // g_opcode201Parameters.u1
+ // g_opcode201Parameters.u2
+
+ // TODO: Fill in Function...
+ break;
+ case kDemoStack:
+ // Used on Card 2001, 2002 and 2003
+
+ // TODO: Fill in Function...
+ break;
+ }
+ }
+}
+
+void MystScriptParser::opcode_201_disable() {
+ g_opcode201Parameters.enabled = false;
+ g_opcode201Parameters.soundId = 0;
+ g_opcode201Parameters.lastVar105 = 0;
+}
+
+void MystScriptParser::opcode_201(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kIntroStack:
+ _vm->_system->delayMillis(4 * 1000);
+ _vm->_gfx->copyImageToScreen(4, Common::Rect(0, 0, 544, 333));
+ break;
+ case kSeleniticStack:
+ // Used for Card 1191 (Maze Runner)
+
+ if (argc == 0) {
+ g_opcode201Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kStoneshipStack:
+ varUnusedCheck(op, var);
+
+ // Used for Card 2013 (Achenar's Rose-Skull Hologram)
+ if (argc == 0) {
+ g_opcode201Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMystStack:
+ // Used for Cards 4257, 4260, 4263, 4266, 4269, 4272, 4275 and 4278 (Ship Puzzle Boxes)
+ if (argc == 1) {
+ g_opcode201Parameters.soundId = argv[0];
+ g_opcode201Parameters.lastVar105 = 0;
+ g_opcode201Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMechanicalStack:
+ // Used for Card 6159 (Facing Corridor to Fortress Elevator)
+ if (argc == 3) {
+ g_opcode201Parameters.u0 = argv[0];
+ g_opcode201Parameters.u1 = argv[1];
+ g_opcode201Parameters.u2 = argv[2];
+
+ g_opcode201Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kChannelwoodStack:
+ // Used for Card 3247 (Elevator #1 Movement), 3161 (Bridge Movement), 3259 (Elevator #3 Movement) and 3252 (Elevator #2 Movement)
+ if (argc == 0) {
+ // TODO: Fill in Function. Video Playback? Rect from invoking hotspot resource...
+ if (false) {
+ // Card 3161
+ _vm->_video->playMovie(_vm->wrapMovieFilename("bridge", kChannelwoodStack), 292, 204);
+
+ // Card 3247
+ _vm->_video->playMovie(_vm->wrapMovieFilename("welev1dn", kChannelwoodStack), 214, 107);
+ _vm->_video->playMovie(_vm->wrapMovieFilename("welev1up", kChannelwoodStack), 214, 107);
+
+ // Card 3252
+ _vm->_video->playMovie(_vm->wrapMovieFilename("welev2dn", kChannelwoodStack), 215, 118);
+ _vm->_video->playMovie(_vm->wrapMovieFilename("welev2up", kChannelwoodStack), 215, 118);
+
+ // Card 3259
+ _vm->_video->playMovie(_vm->wrapMovieFilename("welev3dn", kChannelwoodStack), 213, 99);
+ _vm->_video->playMovie(_vm->wrapMovieFilename("welev3up", kChannelwoodStack), 213, 99);
+ }
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kDemoStack:
+ // Used on Card 2001, 2002 and 2003
+ if (argc == 0) {
+ g_opcode201Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+static struct {
+ bool enabled;
+ uint16 var;
+} g_opcode202Parameters;
+
+void MystScriptParser::opcode_202_run(void) {
+ if (g_opcode202Parameters.enabled) {
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ // Used for Card 1191 (Maze Runner)
+
+ // TODO: Fill in function...
+ break;
+ case kDemoPreviewStack:
+ case kMystStack:
+ // Used for Card 4378 (Library Tower Rotation Map)
+ // TODO: Fill in.. Code for Tower Rotation Angle etc..
+ // Var 0, 3, 4, 5, 6, 7, 8, 9 used for Type 8 Image Display
+ // Type 11 Hotspot for control..
+ // Var 304 controls presence of Myst Library Image
+ break;
+ case kMechanicalStack:
+ // Used for Card 6220 (Sirrus' Mechanical Bird)
+ // TODO: Fill in Function
+ break;
+ case kChannelwoodStack:
+ // Used for Cards 3328, 3691, 3731, 3809, 3846 etc. (Water Valves)
+
+ // Code for Water Flow Logic
+ // Var 8 = "Water Tank Valve State"
+ // Controls
+ // Var 19 = "Water Flowing to First Water Valve"
+ // Code for this in Opcode 104 / 122
+
+ // Var 19 = "Water Flowing to First Water Valve"
+ // and
+ // Var 9 = "First Water Valve State"
+ // Controls
+ // Var 20 = "Water Flowing to Second (L) Water Valve"
+ // Var 3 = "Water Flowing (R) to Pump for Upper Walkway to Temple Elevator"
+ uint16 var9 = _vm->_varStore->getVar(9);
+ if (_vm->_varStore->getVar(19)) {
+ _vm->_varStore->setVar(20, !var9);
+ _vm->_varStore->setVar(3, var9);
+ } else {
+ // No water into Valve
+ _vm->_varStore->setVar(20, 0);
+ _vm->_varStore->setVar(3, 0);
+ }
+
+ // Var 20 = "Water Flowing to Second (L) Water Valve"
+ // and
+ // Var 10 = "Second (L) Water Valve State"
+ // Controls
+ // Var 24 = "Water Flowing to Third (L, L) Water Valve"
+ // Var 21 = "Water Flowing to Third (L, R) Water Valve"
+ uint16 var10 = _vm->_varStore->getVar(10);
+ if (_vm->_varStore->getVar(20)) {
+ _vm->_varStore->setVar(24, !var10);
+ _vm->_varStore->setVar(21, var10);
+ } else {
+ // No water into Valve
+ _vm->_varStore->setVar(24, 0);
+ _vm->_varStore->setVar(21, 0);
+ }
+
+ // Var 21 = "Water Flowing to Third (L, R) Water Valve"
+ // and
+ // Var 11 = "Third (L, R) Water Valve State"
+ // Controls
+ // Var 23 = "Water Flowing to Fourth (L, R, L) Water Valve"
+ // Var 22 = "Water Flowing to Fourth (L, R, R) Water Valve"
+ uint16 var11 = _vm->_varStore->getVar(11);
+ if (_vm->_varStore->getVar(21)) {
+ _vm->_varStore->setVar(23, !var11);
+ _vm->_varStore->setVar(22, var11);
+ } else {
+ // No water into Valve
+ _vm->_varStore->setVar(23, 0);
+ _vm->_varStore->setVar(22, 0);
+ }
+
+ // Var 24 = "Water Flowing to Third (L, L) Water Valve"
+ // and
+ // Var 14 = "Third (L, L) Water Valve State"
+ // Controls
+ // Var 29 = "Water Flowing to Pipe In Water (L, L, L)"
+ // Var 28 = "Water Flowing to Join and Pump Bridge (L, L, R)"
+ uint16 var14 = _vm->_varStore->getVar(14);
+ if (_vm->_varStore->getVar(24)) {
+ _vm->_varStore->setVar(29, !var14);
+ _vm->_varStore->setVar(28, var14);
+ } else {
+ // No water into Valve
+ _vm->_varStore->setVar(29, 0);
+ _vm->_varStore->setVar(28, 0);
+ }
+
+ // Var 22 = "Water Flowing to Fourth (L, R, R) Water Valve"
+ // and
+ // Var 12 = "Fourth (L, R, R) Water Valve State"
+ // Controls
+ // Var 25 = "Water Flowing to Pipe Bridge (L, R, R, L)"
+ // Var 15 = "Water Flowing (L, R, R, R) to Pump for Lower Walkway to Upper Walkway Elevator"
+ uint16 var12 = _vm->_varStore->getVar(12);
+ if (_vm->_varStore->getVar(22)) {
+ _vm->_varStore->setVar(25, !var12);
+ _vm->_varStore->setVar(15, var12);
+ } else {
+ // No water into Valve
+ _vm->_varStore->setVar(25, 0);
+ _vm->_varStore->setVar(15, 0);
+ }
+
+ // Var 23 = "Water Flowing to Fourth (L, R, L) Water Valve"
+ // and
+ // Var 13 = "Fourth (L, R, L) Water Valve State"
+ // Controls
+ // Var 27 = "Water Flowing to Join and Pump Bridge (L, R, L, L)"
+ // Var 26 = "Water Flowing to Pipe At Entry Point (L, R, L, R)"
+ uint16 var13 = _vm->_varStore->getVar(13);
+ if (_vm->_varStore->getVar(23)) {
+ _vm->_varStore->setVar(27, !var13);
+ _vm->_varStore->setVar(26, var13);
+ } else {
+ // No water into Valve
+ _vm->_varStore->setVar(27, 0);
+ _vm->_varStore->setVar(26, 0);
+ }
+
+ // TODO: Not sure that original had OR logic for water flow at Join...
+ // Var 27 = "Water Flowing to Join and Pump Bridge (L, R, L, L)"
+ // Or
+ // Var 28 = "Water Flowing to Join and Pump Bridge (L, L, R)"
+ // Controls
+ // Var 31 = "Water Flowing to Join (L, L, R)" // 0 to 2 = Stop Sound, Background, Background with Water Flow
+ // Var 7 = "Bridge Pump Running"
+ // TODO: Not sure about control of Var 31 which is tristate...
+ if (_vm->_varStore->getVar(27) || _vm->_varStore->getVar(28)) {
+ _vm->_varStore->setVar(31, 2); // Background with Water Flow
+ _vm->_varStore->setVar(7, 1);
+ } else {
+ // No water into Valve
+ _vm->_varStore->setVar(31, 1); // Background
+ _vm->_varStore->setVar(7, 0);
+ }
+
+ // TODO: Code for this shouldn't be here...
+ // Move to Opcodes called by Pipe Extension...
+ // Var 25 = "Water Flowing to Pipe Bridge (L, R, R, L)"
+ // and
+ // Var 6 = "Pipe Bridge Extended"
+ // Controls
+ // Var 32 = "Water Flowing (L, R, R, L, Pipe) State" }, // 0 to 2 = Stop Sound, Background, Background with Water Flow
+ // Var 4 = "Water Flowing (L, R, R, L, Pipe Extended) to Pump for Book Room Elevator"
+ // TODO: Not sure about control of Var 32 which is tristate...
+ if (_vm->_varStore->getVar(25) && _vm->_varStore->getVar(6)) {
+ _vm->_varStore->setVar(32, 2); // Background with Water Flow
+ _vm->_varStore->setVar(4, 1);
+ } else {
+ // No water into Valve
+ _vm->_varStore->setVar(32, 1); // Background
+ _vm->_varStore->setVar(4, 0);
+ }
+ break;
+ }
+ }
+}
+
+void MystScriptParser::opcode_202_disable(void) {
+ g_opcode202Parameters.enabled = false;
+}
+
+void MystScriptParser::opcode_202(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ varUnusedCheck(op, var);
+
+ // Used for Card 1191 (Maze Runner)
+ if (argc == 0) {
+ g_opcode202Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kStoneshipStack:
+ varUnusedCheck(op, var);
+
+ // Used for Card 2160 (Lighthouse Battery Pack Closeup)
+ // TODO: Implement Code...
+ // Not Sure of Purpose - Update of Light / Discharge?
+ unknown(op, var, argc, argv);
+ break;
+ case kDemoPreviewStack:
+ case kMystStack:
+ varUnusedCheck(op, var);
+
+ // Used for Card 4378 (Library Tower Rotation Map)
+ if (argc == 1) {
+ // TODO: Figure Out argv[0] purpose.. number of image resources?
+ g_opcode202Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMechanicalStack:
+ // Used for Card 6220 (Sirrus' Mechanical Bird)
+ if (argc == 0) {
+ g_opcode202Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kChannelwoodStack:
+ // Used for Cards 3328, 3691, 3731, 3809, 3846 etc. (Water Valves)
+ if (argc == 0) {
+ g_opcode202Parameters.var = var;
+ g_opcode202Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ varUnusedCheck(op, var);
+
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+static struct {
+ bool enabled;
+} g_opcode203Parameters;
+
+void MystScriptParser::opcode_203_run(void) {
+ if (g_opcode203Parameters.enabled) {
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ // Used for Card 1245 (Sound Receiver)
+ // TODO: Fill in Logic to Change Viewer Display etc.?
+ break;
+ case kMystStack:
+ // Used for Card 4138 (Dock Forechamber Door)
+ // TODO: Fill in Logic..
+ break;
+ case kMechanicalStack:
+ // Used for Card 6043 (Weapons Rack with Snake Box)
+ // TODO: Fill in Logic for Snake Box...
+ break;
+ case kChannelwoodStack:
+ // Used for Card 3310 (Sirrus' Room Right Bed Drawer),
+ // Card 3307 (Sirrus' Room Left Bed Drawer)
+ // and Card 3318 (Sirrus' Room Nightstand Drawer)
+ // TODO: Fill in Logic...
+ break;
+ }
+ }
+}
+
+void MystScriptParser::opcode_203_disable(void) {
+ g_opcode203Parameters.enabled = false;
+}
+
+void MystScriptParser::opcode_203(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ uint16 imageIdDarkDoorOpen = 0;
+ uint16 imageIdDarkDoorClosed = 0;
+
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ // Used for Card 1245 (Sound Receiver)
+ if (argc == 0) {
+ g_opcode203Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kStoneshipStack:
+ // Used for all/most Cards in Tunnels Down To Brothers Rooms
+
+ // TODO: Duplicate or similar function to Opcode 203?
+ if (argc == 2 || argc == 4) {
+ debugC(kDebugScript, "Opcode %d: %d Arguments", op, argc);
+
+ uint16 u0 = argv[0];
+ if (argc == 4) {
+ imageIdDarkDoorOpen = argv[1];
+ imageIdDarkDoorClosed = argv[2];
+ }
+ uint16 soundIdAlarm = argv[argc - 1];
+
+ debugC(kDebugScript, "\tu0: %d", u0);
+ if (argc == 4) {
+ debugC(kDebugScript, "\timageIdDarkDoorOpen: %d", imageIdDarkDoorOpen);
+ debugC(kDebugScript, "\tsoundIdDarkDoorClosed: %d", imageIdDarkDoorClosed);
+ }
+ debugC(kDebugScript, "\tsoundIdAlarm: %d", soundIdAlarm);
+
+ // TODO: Fill in Correct Function for Lights
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMystStack:
+ // Used for Card 4138 (Dock Forechamber Door)
+ if (argc == 0) {
+ g_opcode203Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMechanicalStack:
+ // Used for Card 6043 (Weapons Rack with Snake Box)
+ if (argc == 0) {
+ g_opcode203Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kChannelwoodStack:
+ // Used for Card 3310 (Sirrus' Room Right Bed Drawer),
+ // Card 3307 (Sirrus' Room Left Bed Drawer)
+ // and Card 3318 (Sirrus' Room Nightstand Drawer)
+ if (argc == 0) {
+ g_opcode203Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+static struct {
+ bool enabled;
+ uint16 soundId;
+} g_opcode204Parameters;
+
+void MystScriptParser::opcode_204_run(void) {
+ if (g_opcode204Parameters.enabled) {
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ // Used for Card 1147 (Sound Code Lock)
+ // TODO: Fill in code for Sound Lock...
+ break;
+ case kMystStack:
+ // Used for Card 4134 and 4149 (Dock)
+ // TODO: Fill in..
+ break;
+ case kMechanicalStack:
+ // TODO: Fill in Logic.
+ // Var 12 holds Large Cog Position in range 0 to 5
+ // - For animation
+ // Var 11 holds C position in range 0 to 9
+ // - 4 for Correct Answer
+ // C Movement Sound
+ //_vm->_sound->playSound(g_opcode204Parameters.soundId);
+ break;
+ }
+ }
+}
+
+void MystScriptParser::opcode_204_disable(void) {
+ g_opcode204Parameters.enabled = false;
+}
+
+void MystScriptParser::opcode_204(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ // Used for Card 1147 (Sound Code Lock)
+ if (argc == 0) {
+ g_opcode204Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kStoneshipStack:
+ // Used for Card 2160 (Lighthouse Battery Pack Closeup)
+ if (argc == 0) {
+ // TODO: Implement Code For Battery Meter Level
+ // Overwrite _vm->_resources[1]->_subImages[0].rect.bottom 1 to 80
+ // Add accessor functions for this...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMystStack:
+ // Used for Card 4134 and 4149 (Dock)
+ // TODO: Fill in logic.. Unsure of exact function to trigger and location on screen..
+ if (false) {
+ // Card 4134
+ _vm->_video->playMovie(_vm->wrapMovieFilename("birds1", kMystStack), 416, 0);
+
+ // Card 4149
+ _vm->_video->playMovie(_vm->wrapMovieFilename("birds2", kMystStack), 433, 0);
+
+ // Unsure...
+ _vm->_video->playMovie(_vm->wrapMovieFilename("birds3", kMystStack), 0, 0);
+ }
+ break;
+ case kMechanicalStack:
+ // Used for Card 6180 (Lower Elevator Puzzle)
+ if (argc == 1) {
+ g_opcode204Parameters.soundId = argv[0];
+ g_opcode204Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+static struct {
+ uint16 soundIdPosition[4];
+
+ bool enabled;
+} g_opcode205Parameters;
+
+void MystScriptParser::opcode_205_run(void) {
+ if (g_opcode205Parameters.enabled) {
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ // Used for Card 1191 (Maze Runner)
+ // TODO: Fill in function...
+ break;
+ case kMystStack:
+ // Used for Card 4532 (Rocketship Piano)
+ // TODO: Fill in function...
+ break;
+ case kMechanicalStack:
+ // Used for Card 6156 (Fortress Rotation Controls)
+ // TODO: Fill in function...
+ // g_opcode205Parameters.soundIdPosition[4]
+ break;
+ }
+ }
+}
+
+void MystScriptParser::opcode_205_disable(void) {
+ g_opcode205Parameters.enabled = false;
+}
+
+void MystScriptParser::opcode_205(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ uint16 imageIdDoorOpen = 0;
+ uint16 imageIdDoorClosed = 0;
+
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ // Used for Card 1191 (Maze Runner)
+
+ if (argc == 0) {
+ g_opcode205Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kStoneshipStack:
+ // Used on Cards 2322, 2285 (Tunnels Down To Brothers Rooms)
+
+ // TODO: Duplicate or similar function to Opcode 203?
+ if (argc == 2 || argc == 4) {
+ debugC(kDebugScript, "Opcode %d: %d Arguments", op, argc);
+
+ uint16 u0 = argv[0];
+ if (argc == 4) {
+ imageIdDoorOpen = argv[1];
+ imageIdDoorClosed = argv[2];
+ }
+ uint16 soundIdAlarm = argv[argc - 1];
+
+ debugC(kDebugScript, "\tu0: %d", u0);
+ if (argc == 4) {
+ debugC(kDebugScript, "\timageIdDoorOpen: %d", imageIdDoorOpen);
+ debugC(kDebugScript, "\tsoundIdDoorClosed: %d", imageIdDoorClosed);
+ }
+ debugC(kDebugScript, "\tsoundIdAlarm: %d", soundIdAlarm);
+
+ // TODO: Fill in Correct Function for Lights
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMystStack:
+ // Used for Card 4532 (Rocketship Piano)
+
+ if (argc == 0) {
+ g_opcode205Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMechanicalStack:
+ // Used for Card 6156 (Fortress Rotation Controls)
+
+ if (argc == 4) {
+ g_opcode205Parameters.soundIdPosition[0] = argv[0];
+ g_opcode205Parameters.soundIdPosition[1] = argv[1];
+ g_opcode205Parameters.soundIdPosition[2] = argv[2];
+ g_opcode205Parameters.soundIdPosition[3] = argv[3];
+
+ g_opcode205Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+static struct {
+ uint16 soundIdStart[2];
+ uint16 soundIdPosition[4];
+
+ bool enabled;
+} g_opcode206Parameters;
+
+void MystScriptParser::opcode_206_run(void) {
+ if (g_opcode206Parameters.enabled) {
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ // Used for Card 1191 (Maze Runner)
+ // TODO: Fill in function...
+ break;
+ case kMechanicalStack:
+ // Used for Card 6044 (Fortress Rotation Simulator)
+
+ // g_opcode206Parameters.soundIdStart[2]
+ // g_opcode206Parameters.soundIdPosition[4]
+
+ // TODO: Fill in function...
+ break;
+ }
+ }
+}
+
+void MystScriptParser::opcode_206_disable(void) {
+ g_opcode206Parameters.enabled = false;
+}
+
+void MystScriptParser::opcode_206(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kSeleniticStack:
+ // Used for Card 1191 (Maze Runner)
+
+ if (argc == 0) {
+ g_opcode206Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kStoneshipStack:
+ // Used for Cards 2272 and 2234 (Facing Out of Door)
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Unknown, %d Arguments", op, argc);
+ // TODO: Function Unknown...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMystStack:
+ // Used for Card 4256 (Butterfly Movie Activation)
+ // TODO: Implement Logic...
+ break;
+ case kMechanicalStack:
+ // Used for Card 6044 (Fortress Rotation Simulator)
+ if (argc == 6) {
+ g_opcode206Parameters.soundIdStart[0] = argv[0];
+ g_opcode206Parameters.soundIdStart[1] = argv[1];
+ g_opcode206Parameters.soundIdPosition[0] = argv[2];
+ g_opcode206Parameters.soundIdPosition[1] = argv[3];
+ g_opcode206Parameters.soundIdPosition[2] = argv[4];
+ g_opcode206Parameters.soundIdPosition[3] = argv[5];
+
+ g_opcode206Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_207(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kStoneshipStack:
+ // Used for Card 2138 (Lighthouse Key/Chest Animation Logic)
+ // TODO: Fill in function
+ warning("TODO: Opcode 207 Lighthouse Key/Chest Animation Logic");
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_208(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kStoneshipStack:
+ varUnusedCheck(op, var);
+
+ // Used in Card 2218 (Telescope view)
+ if (argc == 3) {
+ debugC(kDebugScript, "Opcode %d: Telescope View", op);
+ uint16 imagePanorama = argv[0];
+ uint16 imageLighthouseOff = argv[1];
+ uint16 imageLighthouseOn = argv[2];
+
+ debugC(kDebugScript, "Image (Panorama): %d", imagePanorama);
+ debugC(kDebugScript, "Image (Lighthouse Off): %d", imageLighthouseOff);
+ debugC(kDebugScript, "Image (Lighthouse On): %d", imageLighthouseOn);
+
+ // TODO: Fill in Logic.
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMystStack:
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Imager Function", op);
+ debugC(kDebugScript, "Var: %d", var);
+
+ // TODO: Fill in Correct Function
+ if (false) {
+ _vm->_video->playMovie(_vm->wrapMovieFilename("vltmntn", kMystStack), 159, 97);
+ }
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+static struct {
+ uint16 u0[5];
+ uint16 u1[5];
+ uint16 stateVar;
+
+ uint16 soundId;
+
+ bool enabled;
+} g_opcode209Parameters;
+
+void MystScriptParser::opcode_209_run(void) {
+ static bool enabledLast;
+
+ if (g_opcode209Parameters.enabled) {
+ switch (_vm->getCurStack()) {
+ case kStoneshipStack:
+ // Used for Card 2004 (Achenar's Room Drawers)
+
+ // TODO: Implement Function...
+ // Swap Open Drawers?
+ break;
+ case kDemoPreviewStack:
+ case kMystStack:
+ // Used for Card 4334 and 4348 (Myst Library Bookcase Door)
+ if (!enabledLast)
+ // TODO: If Variable changed...
+ _vm->_sound->playSound(g_opcode209Parameters.soundId);
+
+ // TODO: Code to trigger Type 6 to play movie...
+ break;
+ case kMechanicalStack:
+ // Used for Card 6044 (Fortress Rotation Simulator)
+
+ // TODO: Implement Function For Secret Panel State as
+ // per Opcode 200 function (Mechanical)
+ break;
+ default:
+ break;
+ }
+ }
+
+ enabledLast = g_opcode209Parameters.enabled;
+}
+
+void MystScriptParser::opcode_209_disable(void) {
+ g_opcode209Parameters.enabled = false;
+}
+
+void MystScriptParser::opcode_209(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kStoneshipStack:
+ // Used for Card 2004 (Achenar's Room Drawers)
+ if (argc == 11) {
+ g_opcode209Parameters.u0[0] = argv[0];
+ g_opcode209Parameters.u0[1] = argv[1];
+ g_opcode209Parameters.u0[2] = argv[2];
+ g_opcode209Parameters.u0[3] = argv[3];
+ g_opcode209Parameters.u0[4] = argv[4];
+
+ g_opcode209Parameters.u1[0] = argv[5];
+ g_opcode209Parameters.u1[1] = argv[6];
+ g_opcode209Parameters.u1[2] = argv[7];
+ g_opcode209Parameters.u1[3] = argv[8];
+ g_opcode209Parameters.u1[4] = argv[9];
+
+ g_opcode209Parameters.stateVar = argv[10];
+
+ g_opcode209Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kDemoPreviewStack:
+ case kMystStack:
+ // Used for Card 4334 and 4348 (Myst Library Bookcase Door)
+ if (argc == 1) {
+ g_opcode209Parameters.soundId = argv[0];
+ g_opcode209Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMechanicalStack:
+ // Used for Card 6044 (Fortress Rotation Simulator)
+ if (argc == 0) {
+ g_opcode209Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+static struct {
+ bool enabled;
+} g_opcode210Parameters;
+
+void MystScriptParser::opcode_210_run(void) {
+ if (g_opcode210Parameters.enabled) {
+ // Code for Generator Puzzle
+
+ // Var 52 to 61 Hold Button State for 10 generators
+ // Var 64, 65 - 2 8-Segments for Rocket Power Dial
+ // Var 62, 63 - 2 8-Segments for Power Dial
+ // Var 96, 97 - Needle for Power and Rocket Power Dials
+
+ // Var 44 Holds State for Rocketship
+ // 0 = No Power
+ // 1 = Insufficient Power
+ // 2 = Correct Power i.e. 59V
+
+ // Var 93 Holds Breaker nearest Generator State
+ // Var 94 Holds Breaker nearest Rocket Ship State
+ // 0 = Closed 1 = Open
+
+ const uint16 correctVoltage = 59;
+
+ // Correct Solution is 4, 7, 8, 9 i.e. 16 + 2 + 22 + 19 = 59
+ const uint16 genVoltages[10] = { 10, 7, 8, 16, 5, 1, 2, 22, 19, 9 };
+
+ uint16 powerVoltage = 0;
+ uint16 rocketPowerVoltage = 0;
+
+ // Calculate Power Voltage from Generator Contributions
+ for (byte i = 0; i < ARRAYSIZE(genVoltages); i++)
+ if (_vm->_varStore->getVar(52 + i))
+ powerVoltage += genVoltages[i];
+
+ // Logic for Var 49 - Generator Running Sound Control
+ if (powerVoltage == 0)
+ _vm->_varStore->setVar(49, 0);
+ else
+ _vm->_varStore->setVar(49, 1);
+
+ // TODO: Animation Code to Spin Up and Spin Down LED Dials?
+ // Code For Power Dial Var 62 and 63
+ _vm->_varStore->setVar(62, powerVoltage / 10);
+ _vm->_varStore->setVar(63, powerVoltage % 10);
+ // TODO: Var 96 - Power Needle Logic
+
+ // Code For Breaker Logic
+ if (_vm->_varStore->getVar(93) != 0 || _vm->_varStore->getVar(94) != 0)
+ rocketPowerVoltage = 0;
+ else {
+ if (powerVoltage <= correctVoltage)
+ rocketPowerVoltage = powerVoltage;
+ else {
+ // Blow Generator Room Breaker...
+ _vm->_varStore->setVar(93, 1);
+ // TODO: I think Logic For Blowing Other Breaker etc.
+ // is done in process on Breaker Cards.
+
+ rocketPowerVoltage = 0;
+ }
+ }
+
+ // TODO: Animation Code to Spin Up and Spin Down LED Dials?
+ // Code For Rocket Power Dial
+ _vm->_varStore->setVar(64, rocketPowerVoltage / 10);
+ _vm->_varStore->setVar(65, rocketPowerVoltage % 10);
+ // TODO: Var 97 - Rocket Power Needle Logic
+
+ // Set Rocket Ship Power Based on Power Level
+ if (rocketPowerVoltage == 0)
+ _vm->_varStore->setVar(44, 0);
+ else if (rocketPowerVoltage < correctVoltage)
+ _vm->_varStore->setVar(44, 1);
+ else if (rocketPowerVoltage == correctVoltage)
+ _vm->_varStore->setVar(44, 2);
+ else // Should Not Happen Case
+ _vm->_varStore->setVar(44, 0);
+ }
+}
+
+void MystScriptParser::opcode_210_disable(void) {
+ g_opcode210Parameters.enabled = false;
+}
+
+void MystScriptParser::opcode_210(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kStoneshipStack:
+ varUnusedCheck(op, var);
+
+ // Used in Cards 2205 and 2207 (Cloud Orbs in Sirrus' Room)
+ if (argc == 2) {
+ uint16 soundId = argv[0];
+ uint16 soundIdStopping = argv[1];
+
+ // TODO: Work Out Function i.e. control Var etc.
+ if (false) {
+ _vm->_sound->playSound(soundId);
+ _vm->_sound->playSound(soundIdStopping);
+ }
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ case kMystStack:
+ // Used for Card 4297 (Generator Puzzle)
+ if (argc == 2) {
+ // TODO: Work Out 2 parameters meaning... 16, 17
+ // Script Resources for Generator Spinup and Spindown Sounds?
+ g_opcode210Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+static struct {
+ bool enabled;
+} g_opcode211Parameters;
+
+void MystScriptParser::opcode_211_run(void) {
+ const uint16 imageBaseId = 4779;
+ const uint16 gridBaseLeft = 173;
+ const uint16 gridBaseTop = 168;
+
+ static uint16 lastGridState[6];
+ uint16 gridState[6];
+ uint16 image;
+
+ if (g_opcode211Parameters.enabled) {
+ // Grid uses Var 17 to 22 as bitfields (8 horizontal cells x 6 vertical)
+ for (byte i = 0; i < 6; i++) {
+ gridState[i] = _vm->_varStore->getVar(i + 17);
+
+ if (gridState[i] != lastGridState[i]) {
+ for (byte j = 0; j < 8; j++) {
+ // TODO: Animation Code
+ if ((gridState[i] >> (7 - j)) & 1)
+ image = 16;
+ else
+ image = 0;
+
+ _vm->_gfx->copyImageToScreen(imageBaseId + image, Common::Rect(gridBaseLeft + (j * 26), gridBaseTop + (i * 26), gridBaseLeft + ((j + 1) * 26), gridBaseTop + ((i + 1) * 26)));
+ }
+ }
+
+ lastGridState[i] = gridState[i];
+ }
+
+ // Var 23 contains boolean for whether pattern matches correct book pattern i.e. Pattern 158
+ if (gridState[0] == 0xc3 && gridState[1] == 0x6b && gridState[2] == 0xa3 &&
+ gridState[3] == 0x93 && gridState[4] == 0xcc && gridState[5] == 0xfa)
+ _vm->_varStore->setVar(23, 1);
+ else
+ _vm->_varStore->setVar(23, 0);
+ }
+}
+
+void MystScriptParser::opcode_211_disable(void) {
+ g_opcode211Parameters.enabled = false;
+}
+
+void MystScriptParser::opcode_211(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kDemoPreviewStack:
+ case kMystStack:
+ // Used for Card 4059 (Fireplace Puzzle)
+ if (argc == 0) {
+ for (byte i = 0; i < 6; i++)
+ _vm->_varStore->setVar(i + 17, 0);
+
+ g_opcode211Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+static struct {
+ bool enabled;
+} g_opcode212Parameters;
+
+void MystScriptParser::opcode_212_run(void) {
+ if (g_opcode212Parameters.enabled) {
+ // TODO: Implement Correct Code for Myst Clock Tower Cog Puzzle
+ // Card 4113
+
+ if (false) {
+ // 3 videos to be played of Cog Movement
+ // TODO: Not 100% sure of movie positions.
+ _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wg1", kMystStack), 220, 50);
+ _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wg2", kMystStack), 220, 80);
+ _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wg3", kMystStack), 220, 110);
+
+ // 1 video of weight descent
+ _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wlfch", kMystStack), 123, 0);
+
+ // Video of Cog Open on Success
+ _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wggat", kMystStack), 195, 225);
+ // Var 40 set on success
+ _vm->_varStore->setVar(40, 1);
+ }
+ }
+}
+
+void MystScriptParser::opcode_212_disable(void) {
+ g_opcode212Parameters.enabled = false;
+}
+
+void MystScriptParser::opcode_212(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4113 (Clock Tower Cog Puzzle)
+ if (argc == 0) {
+ g_opcode212Parameters.enabled = true;
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_213(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4524 (Dockside Facing Towards Ship)
+ if (argc == 0) {
+ // TODO: Implement Code...
+ // Code for Gull Videos?
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_214(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4500 (Stellar Observatory)
+ if (argc == 5) {
+ debugC(kDebugScript, "Opcode %d: Unknown...", op);
+
+ uint16 u0 = argv[0];
+ uint16 u1 = argv[1];
+ uint16 u2 = argv[2];
+ uint16 u3 = argv[3];
+ uint16 u4 = argv[4];
+
+ debugC(kDebugScript, "\tu0: %d", u0);
+ debugC(kDebugScript, "\tu1: %d", u1);
+ debugC(kDebugScript, "\tu2: %d", u2);
+ debugC(kDebugScript, "\tu3: %d", u3);
+ debugC(kDebugScript, "\tu4: %d", u4);
+ // TODO: Complete Implementation...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_215(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4134 (Dock Facing Marker Switch)
+ // TODO: Fill in logic. Logic for Gull Videos?
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_216(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4571 (Channelwood Tree)
+ if (argc == 0) {
+ // TODO: Fill in logic for Tree Position From Far...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_217(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4601 (Channelwood Tree)
+ if (argc == 2) {
+ // TODO: Fill in logic for Tree Position Close Up...
+ // 2 arguments: 4, 4
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_218(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4097 (Cabin Boiler)
+ // TODO: Fill in logic
+ if (false) {
+ _vm->_video->playMovie(_vm->wrapMovieFilename("cabfirfr", kMystStack), 254, 244);
+ _vm->_video->playMovie(_vm->wrapMovieFilename("cabcgfar", kMystStack), 254, 138);
+ }
+
+ // Used for Card 4098 (Cabin Boiler)
+ // TODO: Fill in logic
+ if (false) {
+ _vm->_video->playMovie(_vm->wrapMovieFilename("cabfire", kMystStack), 240, 279);
+ _vm->_video->playMovie(_vm->wrapMovieFilename("cabingau", kMystStack), 243, 97);
+ }
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_219(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4530 (Rocketship Music Puzzle)
+ if (argc == 5) {
+ debugC(kDebugScript, "Opcode %d: Unknown...", op);
+
+ uint16 u0 = argv[0];
+ uint16 u1 = argv[1];
+ uint16 u2 = argv[2];
+ uint16 u3 = argv[3];
+ uint16 u4 = argv[4];
+
+ debugC(kDebugScript, "\tu0: %d", u0);
+ debugC(kDebugScript, "\tu1: %d", u1);
+ debugC(kDebugScript, "\tu2: %d", u2);
+ debugC(kDebugScript, "\tu3: %d", u3);
+ debugC(kDebugScript, "\tu4: %d", u4);
+ // TODO: Fill in logic...
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_220(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4530 (Rocketship Music Puzzle Video)
+ // TODO: Fill in logic.
+ if (false) {
+ // loop?
+ _vm->_video->playMovie(_vm->wrapMovieFilename("selenbok", kMystStack), 224, 41);
+ }
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_221(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4168 (Green Book Movies)
+ // Movie plays in resource #0 rect
+ // TODO: Not sure if subsection is looped...
+ if (!_vm->_varStore->getVar(302)) {
+ // HACK: Stop Wind Sounds.. Think this is a problem at library entrance.
+ _vm->_sound->stopSound();
+ _vm->_video->playBackgroundMovie(_vm->wrapMovieFilename("atrusbk1", kMystStack), 314, 76);
+ _vm->_varStore->setVar(302, 1);
+ } else {
+ // HACK: Stop Wind Sounds.. Think this is a problem at library entrance.
+ _vm->_sound->stopSound();
+ _vm->_video->playBackgroundMovie(_vm->wrapMovieFilename("atrusbk2", kMystStack), 314, 76);
+ }
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_222(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4141 (Myst Dock Facing Sea)
+ if (argc == 0) {
+ // TODO: Logic for Gull Videos?
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_298(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kDemoPreviewStack:
+ // Used for Card 3000 (Closed Myst Book)
+ // TODO: Fill in logic.
+ // Start Voice Over... which controls book opening
+ _vm->_sound->playSound(3001);
+
+ // then link to Myst - Trigger of Hotspot? then opcode 199/196/197 for voice over continue?
+ // TODO: Sync Voice and Actions to Original
+ // TODO: Flash Library Red
+ // TODO: Move to run process based delay to prevent
+ // blocking...
+ _vm->_system->delayMillis(20 * 1000);
+ for (uint16 imageId = 3001; imageId <= 3012; imageId++) {
+ _vm->_gfx->copyImageToScreen(imageId, Common::Rect(0, 0, 544, 333));
+ _vm->_system->delayMillis(5 * 1000);
+ }
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_299(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kDemoPreviewStack:
+ // Used for Card 3002 (Myst Island Overview)
+ // TODO: Fill in logic.
+ // Zoom into Island?
+ // On this card is a Type 8 controlled by Var 0, which
+ // can change the Myst Library to Red..
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_300(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kIntroStack:
+ varUnusedCheck(op, var);
+ // TODO: StopSound?
+ unknown(op, var, argc, argv);
+ break;
+ case kDemoPreviewStack:
+ case kMystStack:
+ // Used in Card 4371 (Blue Book) Var = 101
+ // and Card 4363 (Red Book) Var = 100
+ // TODO: Fill in Logic
+ debugC(kDebugScript, "Opcode %d: Book Exit Function...", op);
+ debugC(kDebugScript, "Var: %d", var);
+ break;
+ case kStoneshipStack:
+ // Used in Card 2218 (Telescope view)
+ varUnusedCheck(op, var);
+ // TODO: Fill in Logic. Clearing Variable for View?
+ break;
+ case kMechanicalStack:
+ // Used in Card 6156 (Fortress Elevator View)
+ varUnusedCheck(op, var);
+ // TODO: Fill in Logic. Clearing Variable for View?
+ break;
+ case kChannelwoodStack:
+ // Used in Card 3012 (Achenar's Holoprojector Control)
+ varUnusedCheck(op, var);
+ // TODO: Fill in Logic. Clearing Variable for View?
+ break;
+ case kDniStack:
+ // Used in Card 5014 (Atrus Writing)
+ varUnusedCheck(op, var);
+ // TODO: Fill in Logic.
+ break;
+ case kDemoStack:
+ // Used on Card 2000
+ varUnusedCheck(op, var);
+
+ // TODO: Fill in Function...
+ break;
+ default:
+ varUnusedCheck(op, var);
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_301(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kDemoPreviewStack:
+ case kMystStack:
+ // Used in Card 4080 (Fireplace Book) and Other Myst Library Books
+ // TODO: Fill in Logic. Clear Variable on Book exit.. or Copy from duplicate..
+ _vm->_varStore->setVar(0, 1);
+ break;
+ default:
+ varUnusedCheck(op, var);
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_302(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used in Card 4113 (Clock Tower Cog Puzzle)
+ // TODO: Fill in Logic
+ break;
+ default:
+ varUnusedCheck(op, var);
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_303(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4141 (Myst Dock Facing Sea)
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Clear Dock Forechamber Door Variable", op);
+ _vm->_varStore->setVar(105, 0);
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_304(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4601 (Channelwood Tree)
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Unknown...", op);
+ // TODO: Logic for clearing variable?
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_305(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4601 (Channelwood Tree)
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Unknown...", op);
+ // TODO: Logic for clearing variable?
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_306(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4098 (Cabin Boiler Puzzle)
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Unknown...", op);
+ // TODO: Logic for clearing variable?
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_307(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4299 (Generator Room Controls)
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Unknown...", op);
+ // TODO: Logic for clearing variable?
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_308(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4530 (Rocketship Music Sliders)
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Unknown...", op);
+ // TODO: Logic for clearing variable?
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_309(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4168 (Green D'ni Book Open), Red Book Open and Blue Book Open
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Unknown...", op);
+ // TODO: Logic for clearing variable?
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+void MystScriptParser::opcode_312(uint16 op, uint16 var, uint16 argc, uint16 *argv) {
+ varUnusedCheck(op, var);
+
+ switch (_vm->getCurStack()) {
+ case kMystStack:
+ // Used for Card 4698 (Dock Forechamber Imager)
+ if (argc == 0) {
+ debugC(kDebugScript, "Opcode %d: Unknown...", op);
+ // TODO: Logic for clearing variable?
+ } else
+ unknown(op, var, argc, argv);
+ break;
+ default:
+ unknown(op, var, argc, argv);
+ break;
+ }
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/myst_scripts.h b/engines/mohawk/myst_scripts.h
new file mode 100644
index 0000000000..39986a3db2
--- /dev/null
+++ b/engines/mohawk/myst_scripts.h
@@ -0,0 +1,226 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MYST_SCRIPTS_H
+#define MYST_SCRIPTS_H
+
+#include "common/scummsys.h"
+#include "common/util.h"
+
+namespace Mohawk {
+
+#define DECLARE_OPCODE(x) void x(uint16 op, uint16 var, uint16 argc, uint16 *argv)
+
+class MohawkEngine_Myst;
+struct MystScriptEntry;
+
+class MystScriptParser {
+public:
+ MystScriptParser(MohawkEngine_Myst *vm);
+ ~MystScriptParser();
+
+ void runScript(uint16 scriptCount, MystScriptEntry *scripts, MystResource* invokingResource = NULL);
+ void runOpcode(uint16 op, uint16 var = 0, uint16 argc = 0, uint16 *argv = NULL);
+ const char *getOpcodeDesc(uint16 op);
+
+ void disableInitOpcodes();
+ void runPersistentOpcodes();
+
+private:
+ MohawkEngine_Myst *_vm;
+
+ typedef void (MystScriptParser::*OpcodeProcMyst)(uint16 op, uint16 var, uint16 argc, uint16* argv);
+
+ struct MystOpcode {
+ uint16 op;
+ OpcodeProcMyst proc;
+ const char *desc;
+ };
+
+ const MystOpcode *_opcodes;
+ void setupOpcodes();
+ MystResource *_invokingResource;
+ uint16 _opcodeCount;
+
+ void varUnusedCheck(uint16 op, uint16 var);
+
+ void opcode_200_run();
+ void opcode_200_disable();
+ void opcode_201_run();
+ void opcode_201_disable();
+ void opcode_202_run();
+ void opcode_202_disable();
+ void opcode_203_run();
+ void opcode_203_disable();
+ void opcode_204_run();
+ void opcode_204_disable();
+ void opcode_205_run();
+ void opcode_205_disable();
+ void opcode_206_run();
+ void opcode_206_disable();
+ void opcode_209_run();
+ void opcode_209_disable();
+ void opcode_210_run();
+ void opcode_210_disable();
+ void opcode_211_run();
+ void opcode_211_disable();
+ void opcode_212_run();
+ void opcode_212_disable();
+
+ DECLARE_OPCODE(unknown);
+
+ DECLARE_OPCODE(toggleBoolean);
+ DECLARE_OPCODE(setVar);
+ DECLARE_OPCODE(altDest);
+ DECLARE_OPCODE(takePage);
+ DECLARE_OPCODE(opcode_4);
+ DECLARE_OPCODE(opcode_6);
+ DECLARE_OPCODE(opcode_7);
+ DECLARE_OPCODE(opcode_8);
+ DECLARE_OPCODE(opcode_9);
+ DECLARE_OPCODE(opcode_14);
+ DECLARE_OPCODE(dropPage);
+ DECLARE_OPCODE(opcode_16);
+ DECLARE_OPCODE(opcode_17);
+ DECLARE_OPCODE(opcode_18);
+ DECLARE_OPCODE(enableHotspots);
+ DECLARE_OPCODE(disableHotspots);
+ DECLARE_OPCODE(opcode_21);
+ DECLARE_OPCODE(opcode_22);
+ DECLARE_OPCODE(opcode_23);
+ DECLARE_OPCODE(playSound);
+ DECLARE_OPCODE(opcode_26);
+ DECLARE_OPCODE(playSoundBlocking);
+ DECLARE_OPCODE(opcode_28);
+ DECLARE_OPCODE(opcode_29_33);
+ DECLARE_OPCODE(opcode_30);
+ DECLARE_OPCODE(opcode_31);
+ DECLARE_OPCODE(opcode_32);
+ DECLARE_OPCODE(opcode_34);
+ DECLARE_OPCODE(opcode_35);
+ DECLARE_OPCODE(changeCursor);
+ DECLARE_OPCODE(hideCursor);
+ DECLARE_OPCODE(showCursor);
+ DECLARE_OPCODE(opcode_39);
+ DECLARE_OPCODE(changeStack);
+ DECLARE_OPCODE(opcode_41);
+ DECLARE_OPCODE(opcode_42);
+ DECLARE_OPCODE(opcode_43);
+ DECLARE_OPCODE(opcode_44);
+ DECLARE_OPCODE(opcode_46);
+
+ DECLARE_OPCODE(opcode_100);
+ DECLARE_OPCODE(opcode_101);
+ DECLARE_OPCODE(opcode_102);
+ DECLARE_OPCODE(opcode_103);
+ DECLARE_OPCODE(opcode_104);
+ DECLARE_OPCODE(opcode_105);
+ DECLARE_OPCODE(opcode_106);
+ DECLARE_OPCODE(opcode_107);
+ DECLARE_OPCODE(opcode_108);
+ DECLARE_OPCODE(opcode_109);
+ DECLARE_OPCODE(opcode_110);
+ DECLARE_OPCODE(opcode_111);
+ DECLARE_OPCODE(opcode_112);
+ DECLARE_OPCODE(opcode_113);
+ DECLARE_OPCODE(opcode_114);
+ DECLARE_OPCODE(opcode_115);
+ DECLARE_OPCODE(opcode_116);
+ DECLARE_OPCODE(opcode_117);
+ DECLARE_OPCODE(opcode_118);
+ DECLARE_OPCODE(opcode_119);
+ DECLARE_OPCODE(opcode_120);
+ DECLARE_OPCODE(opcode_121);
+ DECLARE_OPCODE(opcode_122);
+ DECLARE_OPCODE(opcode_123);
+ DECLARE_OPCODE(opcode_124);
+ DECLARE_OPCODE(opcode_125);
+ DECLARE_OPCODE(opcode_126);
+ DECLARE_OPCODE(opcode_127);
+ DECLARE_OPCODE(opcode_128);
+ DECLARE_OPCODE(opcode_129);
+ DECLARE_OPCODE(opcode_130);
+ DECLARE_OPCODE(opcode_131);
+ DECLARE_OPCODE(opcode_132);
+ DECLARE_OPCODE(opcode_133);
+ DECLARE_OPCODE(opcode_147);
+ DECLARE_OPCODE(opcode_164);
+ DECLARE_OPCODE(opcode_169);
+ DECLARE_OPCODE(opcode_181);
+ DECLARE_OPCODE(opcode_182);
+ DECLARE_OPCODE(opcode_183);
+ DECLARE_OPCODE(opcode_184);
+ DECLARE_OPCODE(opcode_185);
+ DECLARE_OPCODE(opcode_196);
+ DECLARE_OPCODE(opcode_197);
+ DECLARE_OPCODE(opcode_198);
+ DECLARE_OPCODE(opcode_199);
+
+ DECLARE_OPCODE(opcode_200);
+ DECLARE_OPCODE(opcode_201);
+ DECLARE_OPCODE(opcode_202);
+ DECLARE_OPCODE(opcode_203);
+ DECLARE_OPCODE(opcode_204);
+ DECLARE_OPCODE(opcode_205);
+ DECLARE_OPCODE(opcode_206);
+ DECLARE_OPCODE(opcode_207);
+ DECLARE_OPCODE(opcode_208);
+ DECLARE_OPCODE(opcode_209);
+ DECLARE_OPCODE(opcode_210);
+ DECLARE_OPCODE(opcode_211);
+ DECLARE_OPCODE(opcode_212);
+ DECLARE_OPCODE(opcode_213);
+ DECLARE_OPCODE(opcode_214);
+ DECLARE_OPCODE(opcode_215);
+ DECLARE_OPCODE(opcode_216);
+ DECLARE_OPCODE(opcode_217);
+ DECLARE_OPCODE(opcode_218);
+ DECLARE_OPCODE(opcode_219);
+ DECLARE_OPCODE(opcode_220);
+ DECLARE_OPCODE(opcode_221);
+ DECLARE_OPCODE(opcode_222);
+ DECLARE_OPCODE(opcode_298);
+ DECLARE_OPCODE(opcode_299);
+
+ DECLARE_OPCODE(opcode_300);
+ DECLARE_OPCODE(opcode_301);
+ DECLARE_OPCODE(opcode_302);
+ DECLARE_OPCODE(opcode_303);
+ DECLARE_OPCODE(opcode_304);
+ DECLARE_OPCODE(opcode_305);
+ DECLARE_OPCODE(opcode_306);
+ DECLARE_OPCODE(opcode_307);
+ DECLARE_OPCODE(opcode_308);
+ DECLARE_OPCODE(opcode_309);
+ DECLARE_OPCODE(opcode_312);
+
+ DECLARE_OPCODE(NOP);
+};
+
+}
+
+#undef DECLARE_OPCODE
+
+#endif
diff --git a/engines/mohawk/myst_vars.cpp b/engines/mohawk/myst_vars.cpp
new file mode 100644
index 0000000000..46951a4388
--- /dev/null
+++ b/engines/mohawk/myst_vars.cpp
@@ -0,0 +1,578 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "mohawk/myst.h"
+#include "mohawk/myst_vars.h"
+
+namespace Mohawk {
+
+// The Myst variable system is complex, and the structure is fairly
+// unknown. The idea of this class is to abstract the variable references
+// from the storage structure until the structure is clear enough that
+// the complexity of this abstraction can be removed.
+
+// The exact organization of local/global, persistent/non-persistent
+// age-specific mapping etc. is currently unknown.
+
+MystVarEntry introVars[] = {
+ { 0, 7, "Age To Link To" } // 0 to 7
+ // 0 = Selenitic
+ // 1 = Stoneship
+ // 2 = Myst (Library Ceiling)
+ // 3 = Mechanical
+ // 4 = Channelwood
+ // 5 = Myst (Start of Movie - Over Sea)
+ // 6 = D'ni
+ // 7 = Myst (End of Movie - On Dock)
+};
+
+MystVarEntry seleniticVars[] = {
+ { 0, 0, "Sound Pickup At Windy Tunnel" }, // 0 to 1 // TODO: Multiple Uses of Var 0?
+ { 1, 0, "Sound Pickup At Volcanic Crack" }, // 0 to 1
+ { 2, 0, "Sound Pickup At Clock" }, // 0 to 1
+ { 3, 0, "Sound Pickup At Water Pool" }, // 0 to 1
+ { 4, 0, "Sound Pickup At Crystal Rocks" }, // 0 to 1
+ { 5, 0, "Sound Receiver Doors" }, // 0 to 1
+ { 6, 0, "Windy Tunnel Lights" }, // 0 to 1
+ { 7, 0, "Maze Runner Porthole View" }, // 0 to 3
+ { 8, 0, "Sound Receiver Screen (Control Variable?)" },
+ { 9, 0, "Sound Receiver Water Pool Button Selected" },
+ { 10, 0, "Sound Receiver Volcanic Crack Button Selected" },
+ { 11, 0, "Sound Receiver Clock Button Selected" },
+ { 12, 0, "Sound Receiver Crystal Rocks Button Selected" },
+ { 13, 0, "Sound Receiver Windy Tunnel Button Selected" },
+ { 14, 0, "Sound Receiver LED Digit #0 (Left)" },
+ { 15, 0, "Sound Receiver LED Digit #1" },
+ { 16, 0, "Sound Receiver LED Digit #2" },
+ { 17, 0, "Sound Receiver LED Digit #3 (Right)" },
+ { 18, 0, "Sound Receiver Green Arrow - Right" },
+ { 19, 0, "Sound Receiver Green Arrow - Left" },
+ { 20, 0, "Sound Lock Slider #1 (Left) (Position?)" },
+ { 21, 0, "Sound Lock Slider #2 (Position?)" },
+ { 22, 0, "Sound Lock Slider #3 (Position?)" },
+ { 23, 0, "Sound Lock Slider #4 (Position?)" },
+ { 24, 0, "Sound Lock Slider #5 (Right) (Position?)" },
+ { 25, 0, "Maze Runner Compass Heading" }, // 0 to 8
+ { 26, 0, "Sound Receiver Sum Button Selected" },
+ { 27, 0, "Maze Runner Red Warning Light" },
+ { 28, 0, "Sound Lock Button State" },
+ { 29, 0, "Maze Runner Door Button State" },
+ { 30, 0, "Maze Runner Door State" },
+ { 31, 0, "Maze Runner Forward Button Lit" }, // 0 to 2
+ { 32, 0, "Maze Runner Left & Right Button Lit" }, // 0 to 2
+ { 33, 0, "Maze Runner Backtrack Button Lit" }, // 0 to 2
+ { 102, 1, "Red Page in Age" }, // 0 to 1
+ { 103, 1, "Blue Page in Age" } // 0 to 1
+};
+
+MystVarEntry stoneshipVars[] = {
+ { 0, 0, "Water Pump Button #3 (Right) / Lighthouse Water Drained" }, // 0 to 2 = Button Up & Unlit, Button Down & Lit, Button Up & Lit
+ { 1, 0, "Water Pump Button #2 / Tunnels To Brothers Rooms Drained" }, // 0 to 2 = Button Up & Unlit, Button Down & Lit, Button Up & Lit
+ { 2, 0, "Water Pump Button #1 (Left) / Ship Cabin Water Drained" }, // 0 to 2 = Button Up & Unlit, Button Down & Lit, Button Up & Lit
+ { 3, 0, "Lighthouse (Water or Chest Floating?)" }, // 0 to 1, Used by Far View
+ { 4, 0, "Lighthouse Water/Chest State" }, // 0 to 2 = Water, No Water, Water & Chest Floating
+ { 5, 2, "Lighthouse Trapdoor State" }, // 0 to 2 = Closed & Unlocked, Open, Closed & Locked
+ { 6, 0, "Lighthouse Chest Valve Position" }, // 0 to 1
+ { 7, 0, "Lighthouse Chest Unlocked" }, // 0 to 1
+ { 8, 0, "Lighthouse Chest Key Position?" }, // 0 to 2, 2 = Bottom of Lighthouse
+// Var 9 Unused
+ { 10, 1, "Lighthouse Chest Full Of Water" }, // 0 to 1
+ { 11, 3, "Lighthouse Key State" }, // 0 to 3 = Closed?, Open & No Key, Open & No Key, Open & Key
+ { 12, 0, "Lighthouse Trapdoor - Holding Key" }, // 0 to 1
+ { 13, 0, "State of Water in Tunnels to Brothers' Rooms" }, // 0 to 2 = Dark & Water, Dark & Drained, Lit & Water
+ { 14, 0, "Tunnels to Brothers' Rooms Lit" }, // 0 to 1
+ { 15, 0, "Side Door in Tunnels To Brother's Rooms Open" }, // 0 to 1
+ { 16, 0, "Underwater Light Lit" }, // 0 to 1
+ { 17, 0, "Sirrus' Room Drawer with Drugs Open" }, // 0 to 1
+ { 18, 0, "Brother Room Door Open" }, // 0 to 1, Used for Door Slam
+ { 19, 0, "Brother Room Door State" }, // 0 to 2 = Closed, Open & Dark, Open & Lit
+ { 20, 0, "Ship Cabin Myst Book Present" }, // 0 to 1
+ { 21, 0, "Brothers Rooms' Chest Of Drawers Drawer State" }, // 0 to 6 (Card 2197) or 0 to 7 (Card 2004)
+ { 22, 0, "Sirrus' Chest of Drawers Drawer #1 (Top) Open" }, // 0 to 1
+ { 23, 0, "Sirrus' Chest of Drawers Drawer #2 Open" }, // 0 to 1
+ { 24, 0, "Sirrus' Chest of Drawers Drawer #3 Open" }, // 0 to 1
+// Var 25 Unused - Replaced by Var 35.
+ { 26, 0, "Sirrus' Chest of Drawers Left Small Drawer Open" }, // 0 to 1
+ { 27, 0, "Sirrus' Chest of Drawers Right Small Drawer Open" }, // 0 to 1
+ { 28, 0, "Telescope View Position" }, // Range Unknown.. 0 to 360?
+ { 29, 0, "Achenar's Room Rose/Skull Hologram Button Lit" }, // 0 to 1
+ { 30, 2, "Light State in Tunnel to Compass Rose Room" }, // 0 to 2 = Lit & Underwater Light Lit, Lit, Dark
+ { 31, 0, "Lighthouse Lamp Room Battery Pack Indicator Light" }, // 0 to 1
+ { 32, 0, "Lighthouse Lamp Room Battery Pack Meter Level" }, // Range Unknown.. // Must be 1 to vertical size of image...
+ { 33, 0, "State of Side Door in Tunnels to Compass Rose Room (Power?)" }, // 0 to 2 = Closed (No Power), Closed (Power), Open
+ { 34, 1, "Achenar's Room Drawer with Torn Note Closed" }, // 0 to 1
+ { 35, 2, "Sirrus' Room Drawer #4 (Bottom) Open and Red Page State" }, // 0 to 2 = Open, Open with Page, Closed
+ { 36, 0, "Ship Cabin Door State" }, // 0 to 2 = Closed, Open & Dark, Open & Lit
+ { 102, 1, "Red Page in Age" }, // 0 to 1
+ { 103, 1, "Blue Page in Age" }, // 0 to 1
+ { 105, 0, "Ship Cabin Door State" }
+};
+
+MystVarEntry mystVars[] = {
+ { 0, 1, "Myst Library Bookcase Closed / Library Exit Open" }, // 0 to 1 // TODO: Multiple Uses of Var 0?
+ { 1, 0, "Myst Library Bookcase Open / Library Exit Blocked" }, // 0 to 1
+ { 2, 0, "Marker Switch Near Cabin" }, // 0 to 1
+ { 3, 0, "Marker Switch Near Clock Tower" }, // 0 to 1
+ { 4, 0, "Marker Switch on Dock" }, // 0 to 1
+ { 5, 0, "Marker Switch Near Ship Pool" }, // 0 to 1
+ { 6, 0, "Marker Switch Near Cogs" }, // 0 to 1
+ { 7, 0, "Marker Switch Near Generator Room" }, // 0 to 1
+ { 8, 0, "Marker Switch Near Stellar Observatory" }, // 0 to 1
+ { 9, 0, "Marker Switch Near Rocket Ship" }, // 0 to 1
+ { 10, 0, "Ship Floating State" }, // 0 to 1
+ { 11, 0, "Cabin Door Open State" },
+ { 12, 0, "Clock Tower Gear Bridge" }, // 0 to 1
+ { 13, 0, "Tower Elevator Sound Control" }, // 0 to 1
+ { 14, 0, "Tower Solution (Key) Plaque" }, // 0 to 4
+ // 0 = None
+ // 1 = 59 V
+ // 2 = 2:40 2-2-1
+ // 3 = October 11, 1984 10:04 AM
+ // January 17, 1207 5:46 AM
+ // November 23, 9791 6:57 PM
+ // 4 = 7, 2, 4
+ { 15, 0, "Tower Window (Book) View" }, // 0 to 6
+ // 0 = Wall
+ // 1 = Rocketship
+ // 2 = Cogs Closed
+ // 3 = Ship Sunk
+ // 4 = Channelwood Tree
+ // 5 = Ship Floating
+ // 6 = Cogs Open
+ { 16, 0, "Tower Window (Book) View From Ladder Top" }, // 0 to 2
+ // 0 = Wall
+ // 1 = Sky
+ // 2 = Sky with Channelwood Tree
+ { 17, 0, "Fireplace Grid Row #1 (Top)" }, // Bitfield 0x00 to 0xFF
+ { 18, 0, "Fireplace Grid Row #2" }, // Bitfield 0x00 to 0xFF
+ { 19, 0, "Fireplace Grid Row #3" }, // Bitfield 0x00 to 0xFF
+ { 20, 0, "Fireplace Grid Row #4" }, // Bitfield 0x00 to 0xFF
+ { 21, 0, "Fireplace Grid Row #5" }, // Bitfield 0x00 to 0xFF
+ { 22, 0, "Fireplace Grid Row #6 (Bottom)" }, // Bitfield 0x00 to 0xFF
+ { 23, 0, "Fireplace Pattern Correct" }, // 0 to 1
+ { 24, 1, "Fireplace Blue Page Present" }, // 0 to 1
+ { 25, 1, "Fireplace Red Page Present" }, // 0 to 1
+ { 26, 0, "Ship Puzzle Box Image (Cross)" }, // 0 to 2
+ { 27, 0, "Ship Puzzle Box Image (Leaf)" }, // 0 to 2
+ { 28, 0, "Ship Puzzle Box Image (Arrow)" }, // 0 to 2
+ { 29, 0, "Ship Puzzle Box Image (Eye)" }, // 0 to 2
+ { 30, 0, "Ship Puzzle Box Image (Snake)" }, // 0 to 2
+ { 31, 0, "Ship Puzzle Box Image (Spider)" }, // 0 to 2
+ { 32, 0, "Ship Puzzle Box Image (Anchor)" }, // 0 to 2
+ { 33, 0, "Ship Puzzle Box Image (Ostrich)" }, // 0 to 2
+ { 34, 2, "Dock Forechamber Imager State" }, // 0 to 2 = Off, Mountain, Water
+ { 35, 5, "Dock Forechamber Imager Control Left Digit" }, // 0 to 9
+ { 36, 6, "Dock Forechamber Imager Control Right Digit" }, // 0 to 9
+ { 37, 0, "Clock Tower Control Wheels Position" }, // 0 to 8
+// Var 38 Unused
+// 39 = TODO: ?
+ { 40, 0, "Cog Close/Open State" },
+ { 41, 0, "Dock Marker Switch Vault State" }, // 0 to 2 = Closed, Open & Page Taken, Open & Page Present
+// Var 42 Unused
+ { 43, 0, "Clock Tower Time" }, // 0 to 143
+ { 44, 0, "Rocket Ship Power State" }, // 0 to 2 = None, Insufficient, Correct
+ { 45, 1, "Dock Forechamber Imager Water Effect Enabled" }, // 0 to 1
+ { 46, 0, "Number Of Pages in Red Book" }, // 0 to 6 = 0-5, Extra
+ { 47, 0, "Number Of Pages in Blue Book" }, // 0 to 6 = 0-5, Extra
+ { 48, 0, "Marker Switch on Dock - Duplicate of Var #4?" }, // 0 to 2
+ { 49, 0, "Generator Running" }, // Boolean used for Sound..
+// 50 = TODO: ?
+ { 51, 2, "Forechamber Imager Movie Control Variable" }, // 0 to 4 = Blank, No Function? / Mountain?, Water, Atrus, Marker Switch
+ { 52, 0, "Generator Switch #1" },
+ { 53, 0, "Generator Switch #2" },
+ { 54, 0, "Generator Switch #3" },
+ { 55, 0, "Generator Switch #4" },
+ { 56, 0, "Generator Switch #5" },
+ { 57, 0, "Generator Switch #6" },
+ { 58, 0, "Generator Switch #7" },
+ { 59, 0, "Generator Switch #8" },
+ { 60, 0, "Generator Switch #9" },
+ { 61, 0, "Generator Switch #10" },
+ { 62, 0, "Generator Power Dial Left LED Digit" }, // 0 to 9
+ { 63, 0, "Generator Power Dial Right LED Digit" }, // 0 to 9
+ { 64, 0, "Generator Power To Spaceship Dial Left LED Digit" }, // 0 to 9
+ { 65, 0, "Generator Power To Spaceship Dial Right LED Digit" }, // 0 to 9
+ { 66, 0, "Generator Room Lights On" }, // Boolean
+ { 67, 9, "Cabin Safe Lock Number #1 - Left" },
+ { 68, 9, "Cabin Safe Lock Number #2" },
+ { 69, 9, "Cabin Safe Lock Number #3 - Right" },
+ { 70, 0, "Cabin Safe Matchbox State" }, // 0 to 2
+ { 71, 1, "Stellar Observatory Lights" },
+ { 72, 0, "Channelwood Tree Position" }, // 0 to 12, 4 for Alcove
+ { 73, 9, "Stellar Observatory Telescope Control - Month" }, // 0 to 11, Not in order...
+ // 0 = OCT, 1 = NOV, 2 = DEC, 3 = JUL, 4 = AUG, 5 = SEP
+ // 6 = APR, 7 = MAY, 8 = JUN, 9 = JAN, 10 = FEB, 11 = MAR
+ { 74, 10, "Stellar Observatory Telescope Control - Day Digit #1 (Left)" }, // 0 to 10 = 0 to 9, Blank
+ { 75, 1, "Stellar Observatory Telescope Control - Day Digit #2 (Right)" }, // 0 to 10 = 0 to 9, Blank
+ { 76, 10, "Stellar Observatory Telescope Control - Year Digit #1 (Left)" }, // 0 to 10 = 0 to 9, Blank
+ { 77, 10, "Stellar Observatory Telescope Control - Year Digit #2" }, // 0 to 10 = 0 to 9, Blank
+ { 78, 10, "Stellar Observatory Telescope Control - Year Digit #3" }, // 0 to 10 = 0 to 9, Blank
+ { 79, 0, "Stellar Observatory Telescope Control - Year Digit #4 (Right)" }, // 0 to 10 = 0 to 9, Blank
+ { 80, 1, "Stellar Observatory Telescope Control - Hour Digit #1 (Left)" }, // 0 to 10 = 0 to 9, Blank
+ { 81, 2, "Stellar Observatory Telescope Control - Hour Digit #2 (Right)" }, // 0 to 10 = 0 to 9, Blank
+ { 82, 0, "Stellar Observatory Telescope Control - Minute Digit #1 (Left)" }, // 0 to 10 = 0 to 9, Blank
+ { 83, 0, "Stellar Observatory Telescope Control - Minute Digit #2 (Right)" }, // 0 to 10 = 0 to 9, Blank
+// 84 to 87 = TODO: ?
+ { 88, 0, "Stellar Observatory Telescope Control - AM/PM Indicator" }, // 0 = AM, 1 = PM
+ { 89, 0, "Stellar Observatory Telescope Control - Slider #1 (Left)" }, // 0 to 2 = Not Present, Dark, Lit
+ { 90, 0, "Stellar Observatory Telescope Control - Slider #2" }, // 0 to 2 = Not Present, Dark, Lit
+ { 91, 0, "Stellar Observatory Telescope Control - Slider #3" }, // 0 to 2 = Not Present, Dark, Lit
+ { 92, 0, "Stellar Observatory Telescope Control - Slider #4 (Right)" }, // 0 to 2 = Not Present, Dark, Lit
+ { 93, 0, "Breaker nearest Generator Room Blown" },
+ { 94, 0, "Breaker nearest Rocket Ship Blown" },
+// 95 = TODO: ?
+ { 96, 0, "Generator Power Dial Needle Position" }, // 0 to 24
+ { 97, 0, "Generator Power To Spaceship Dial Needle Position" }, // 0 to 24
+ { 98, 0, "Cabin Boiler Pilot Light Lit" },
+ { 99, 0, "Cabin Boiler Gas Valve Position" }, // 0 to 5
+ { 100, 0, "Red Book Page State" }, // Bitfield
+ { 101, 0, "Blue Book Page State" }, // Bitfield
+ { 102, 1, "Red Page in Age" }, // 0 to 1
+ { 103, 1, "Blue Page in Age" }, // 0 to 1
+// 104 = TODO: ?
+ { 105, 0, "Clock Tower Door / Ship Box Temp Value" },
+ { 106, 0, "Red / Blue Book State" }, // 0 to 4, 0-3 = Books Present 4 = Books Burnt
+ { 300, 0, "Rocket Ship Music Puzzle Slider State" }, // 0 to 2 = Not Present, Dark, Lit
+ { 301, 0, "Rocket Ship Piano Key Depressed" }, // 0 to 1
+ { 302, 0, "Green Book Opened Before Flag" }, // 0 to 1
+ { 303, 1, "Myst Library Bookcase Closed / Library Exit Open" },
+ { 304, 0, "Myst Library Image Present on Tower Rotation Map" }, // 0 to 1
+ { 305, 0, "Cabin Boiler Lit" },
+ { 306, 0, "Cabin Boiler Steam Sound Control" }, // 0 to 27
+ { 307, 0, "Cabin Boiler Needle Position i.e. Fully Pressurised" }, // 0 to 1
+ { 308, 0, "Cabin Safe Handle Position / Matchbox Temp Value" },
+ { 309, 0, "Red/Blue/Green Book Open" }, // 0 to 1
+ { 310, 0, "Dock Forechamber Imager Control Temp Value?" }
+};
+
+MystVarEntry mechVars[] = {
+ { 0, 0, "Achenar's Room Secret Panel State" }, // TODO: Multiple Uses of Var 0?
+ { 1, 0, "Sirrus's Room Secret Panel State" },
+ { 2, 0, "Achenar's Secret Room Crate Lid Open and Blue Page Present" }, // 0 to 4
+ // 0 = Lid Closed, Blue Page Present
+ // 1 = Lid Closed, Blue Page Not Present
+ // 2 = Lid Open, Blue Page Not Present
+ // 3 = Lid Open, Blue Page Present
+ { 3, 0, "Achenar's Secret Room Crate Lid Open" }, // 0 to 1
+ { 4, 0, "Myst Book Staircase State" },
+ { 5, 0, "Fortress Telescope View" },
+ { 6, 0, "Large Cog Visible Through Distant Fortress Doorway" }, // 0 to 1
+ { 7, 0, "Fortress Elevator Door Rotated to Open" }, // 0 to 1
+// Var 8 Not Used
+// Var 9 Not Used
+ { 10, 0, "Fortress Staircase State" }, // 0 to 1
+ { 11, 0, "Fortress Elevator Rotation Control - Position Indicator" }, // 0 to 9, 4 = Red Open Position
+ { 12, 0, "Fortress Elevator Rotation Control - Top Cog Position" }, // 0 to 5
+ { 13, 0, "Fortress Elevator Vertical Position - View" }, // 0 to 2, Used for View Logic on change from Card 6150
+ { 14, 0, "Fortress Elevator Vertical Position - Button" }, // 0 to 2, Used for Button Logic on Card 6120
+ { 15, 0, "Code Lock State" }, // 0 to 2
+ { 16, 0, "Code Lock Shape #1 (Left)" },
+ { 17, 0, "Code Lock Shape #2" },
+ { 18, 0, "Code Lock Shape #3" },
+ { 19, 0, "Code Lock Shape #4 (Right)" },
+ { 20, 0, "Red Dodecahedron Lit" },
+ { 21, 0, "Green Dodecahedron Lit" },
+ { 22, 0, "Yellow Tetrahedron Lit" },
+ { 23, 0, "In Elevator" }, // 0 to 1
+ { 102, 1, "Red Page in Age" }, // 0 to 1
+ { 103, 1, "Blue Page in Age" } // 0 to 1
+};
+
+MystVarEntry channelwoodVars[] = {
+ { 0, 0, "Multiple Uses..." }, // TODO: Multiple Uses of Var 0?
+ { 1, 0, "Water Pump Bridge State" }, // 0 to 1
+ { 2, 0, "Lower Walkway to Upper Walkway Elevator State" }, // 0 to 1
+ { 3, 0, "Water Flowing (R) to Pump for Upper Walkway to Temple Elevator" }, // 0 to 1
+ { 4, 0, "Water Flowing (L, R, R, L, Pipe Extended) to Pump for Book Room Elevator" }, // 0 to 1
+ { 5, 0, "Lower Walkway to Upper Walkway Spiral Stair Lower Door Open" }, // 0 to 1
+ { 6, 0, "Pipe Bridge Extended" }, // 0 to 1
+ { 7, 0, "Bridge Pump Running" }, // 0 to 1
+ { 8, 0, "Water Tank Valve State" }, // 0 to 1
+ { 9, 0, "First Water Valve State" }, // 0 to 1
+ { 10, 0, "Second (L) Water Valve State" }, // 0 to 1
+ { 11, 0, "Third (L, R) Water Valve State" }, // 0 to 1
+ { 12, 0, "Fourth (L, R, R) Water Valve State" }, // 0 to 1
+ { 13, 0, "Fourth (L, R, L) Water Valve State" }, // 0 to 1
+ { 14, 0, "Third (L, L) Water Valve State" }, // 0 to 1
+ { 15, 0, "Water Flowing (L, R, R, R) to Pump for Lower Walkway to Upper Walkway Elevator" }, // 0 to 1
+ { 16, 0, "Lower Walkway to Upper Walkway Spiral Stair Upper Door Open" }, // 0 to 1
+ { 17, 0, "Achenar's Holoprojector Selection" }, // 0 to 3
+ { 18, 0, "Drawer in Sirrus' Room with Wine Bottles and Torn Note Open" }, // 0 to 1
+ { 19, 0, "Water Flowing to First Water Valve" }, // 0 to 1
+ { 20, 0, "Water Flowing to Second (L) Water Valve" }, // 0 to 1
+ { 21, 0, "Water Flowing to Third (L, R) Water Valve" }, // 0 to 1
+ { 22, 0, "Water Flowing to Fourth (L, R, R) Water Valve" }, // 0 to 1
+ { 23, 0, "Water Flowing to Fourth (L, R, L) Water Valve" }, // 0 to 1
+ { 24, 0, "Water Flowing to Third (L, L) Water Valve" }, // 0 to 1
+ { 25, 0, "Water Flowing to Pipe Bridge (L, R, R, L)" }, // 0 to 1
+ { 26, 0, "Water Flowing to Pipe At Entry Point (L, R, L, R)" }, // 0 to 1
+ { 27, 0, "Water Flowing to Join and Pump Bridge (L, R, L, L)" }, // 0 to 1
+ { 28, 0, "Water Flowing to Join and Pump Bridge (L, L, R)" }, // 0 to 1
+ { 29, 0, "Water Flowing to Pipe In Water (L, L, L)" }, // 0 to 1
+ { 30, 0, "Lower Walkway to Upper Walkway Elevator Door Open" }, // 0 to 1
+ { 31, 0, "Water Flowing to Join (L, L, R)" }, // 0 to 2 = Stop Sound, Background, Background with Water Flow
+ { 32, 0, "Water Flowing (L, R, R, L, Pipe) State" }, // 0 to 2 = Stop Sound, Background, Background with Water Flow
+ { 33, 0, "Lower Walkway to Upper Walkway Spiral Stair Upper Door State" }, // 0 to 2 = Closed, Open, Open but slams behind you.
+ { 102, 1, "Red Page in Age" }, // 0 to 1
+ { 103, 1, "Blue Page in Age" }, // 0 to 1
+ { 105, 0, "Upper Walkway to Temple Elevator Door Open / Temple Iron Door Open" } // 0 to 1, used for slam sound
+};
+
+MystVarEntry dniVars[] = {
+ { 0, 0, "Atrus Gone" }, // 0 to 1 // TODO: Multiple Uses of Var 0?
+ { 1, 0, "Myst Book State" }, // 0 to 2 = Book Inactive, Book Unlinkable, Book Linkable
+ { 2, 0, "Sound Control" }, // 0 to 2 = Win Tune, Lose Tune, Stop Sound
+ { 106, 0, "Atrus State Control" } // 0 to 4 = Atrus Writing, Atrus Holding Out Hand, Atrus Gone, Atrus #2, Atrus #2
+};
+
+MystVarEntry creditsVars[] = {
+ { 0, 0, "Image Control" }, // 0 to 6
+ { 1, 1, "Sound Control" } // 0 to 1 = Win Tune, Lose Tune
+};
+
+MystVar::MystVar(MohawkEngine_Myst *vm) {
+ _vm = vm;
+}
+
+MystVar::~MystVar() {
+}
+
+// Only for use by Save/Load, all other code should use getVar()
+uint16 MystVar::saveGetVar(uint16 stack, uint16 v) {
+ uint16 value = 0;
+ MystVarEntry unknownVar = { v, 0, "Unknown" };
+ const char *desc = NULL;
+ uint16 i;
+
+ switch (stack) {
+ case kIntroStack:
+ for (i = 0; i < ARRAYSIZE(introVars); i++) {
+ if (introVars[i].refNum == v) {
+ value = introVars[i].storage;
+ desc = introVars[i].description;
+ break;
+ }
+ }
+ break;
+ case kSeleniticStack:
+ for (i = 0; i < ARRAYSIZE(seleniticVars); i++) {
+ if (seleniticVars[i].refNum == v) {
+ value = seleniticVars[i].storage;
+ desc = seleniticVars[i].description;
+ break;
+ }
+ }
+ break;
+ case kStoneshipStack:
+ for (i = 0; i < ARRAYSIZE(stoneshipVars); i++) {
+ if (stoneshipVars[i].refNum == v) {
+ value = stoneshipVars[i].storage;
+ desc = stoneshipVars[i].description;
+ break;
+ }
+ }
+ break;
+ case kDemoPreviewStack:
+ case kMystStack:
+ for (i = 0; i < ARRAYSIZE(mystVars); i++) {
+ if (mystVars[i].refNum == v) {
+ value = mystVars[i].storage;
+ desc = mystVars[i].description;
+ break;
+ }
+ }
+ break;
+ case kMechanicalStack:
+ for (i = 0; i < ARRAYSIZE(mechVars); i++) {
+ if (mechVars[i].refNum == v) {
+ value = mechVars[i].storage;
+ desc = mechVars[i].description;
+ break;
+ }
+ }
+ break;
+ case kChannelwoodStack:
+ for (i = 0; i < ARRAYSIZE(channelwoodVars); i++) {
+ if (channelwoodVars[i].refNum == v) {
+ value = channelwoodVars[i].storage;
+ desc = channelwoodVars[i].description;
+ break;
+ }
+ }
+ break;
+ case kDniStack:
+ for (i = 0; i < ARRAYSIZE(dniVars); i++) {
+ if (dniVars[i].refNum == v) {
+ value = dniVars[i].storage;
+ desc = dniVars[i].description;
+ break;
+ }
+ }
+ break;
+ case kCreditsStack:
+ for (i = 0; i < ARRAYSIZE(creditsVars); i++) {
+ if (creditsVars[i].refNum == v) {
+ value = creditsVars[i].storage;
+ desc = creditsVars[i].description;
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (desc == NULL) {
+ for (i = 0; i < _unknown.size(); i++) {
+ if (_unknown[i].refNum == v) {
+ value = _unknown[i].storage;
+ desc = _unknown[i].description;
+ break;
+ }
+ }
+
+ if (desc == NULL) {
+ warning("MystVar::getVar(%d): Unknown variable reference", v);
+ _unknown.push_back(unknownVar);
+ desc = _unknown.back().description;
+ }
+ }
+
+ debugC(kDebugVariable, "MystVar::getVar(%d = %s): %d", v, desc, value);
+ return value;
+}
+
+// Only for use by Save/Load, all other code should use setVar()
+void MystVar::loadSetVar(uint16 stack, uint16 v, uint16 value) {
+ const char *desc = NULL;
+ MystVarEntry unknownVar = { v, value, "Unknown" };
+ uint16 i;
+
+ switch (stack) {
+ case kIntroStack:
+ for (i = 0; i < ARRAYSIZE(introVars); i++) {
+ if (introVars[i].refNum == v) {
+ introVars[i].storage = value;
+ desc = introVars[i].description;
+ break;
+ }
+ }
+ break;
+ case kSeleniticStack:
+ for (i = 0; i < ARRAYSIZE(seleniticVars); i++) {
+ if (seleniticVars[i].refNum == v) {
+ seleniticVars[i].storage = value;
+ desc = seleniticVars[i].description;
+ break;
+ }
+ }
+ break;
+ case kStoneshipStack:
+ for (i = 0; i < ARRAYSIZE(stoneshipVars); i++) {
+ if (stoneshipVars[i].refNum == v) {
+ stoneshipVars[i].storage = value;
+ desc = stoneshipVars[i].description;
+ break;
+ }
+ }
+ break;
+ case kDemoPreviewStack:
+ case kMystStack:
+ for (i = 0; i < ARRAYSIZE(mystVars); i++) {
+ if (mystVars[i].refNum == v) {
+ mystVars[i].storage = value;
+ desc = mystVars[i].description;
+ break;
+ }
+ }
+ break;
+ case kMechanicalStack:
+ for (i = 0; i < ARRAYSIZE(mechVars); i++) {
+ if (mechVars[i].refNum == v) {
+ mechVars[i].storage = value;
+ desc = mechVars[i].description;
+ break;
+ }
+ }
+ break;
+ case kChannelwoodStack:
+ for (i = 0; i < ARRAYSIZE(channelwoodVars); i++) {
+ if (channelwoodVars[i].refNum == v) {
+ channelwoodVars[i].storage = value;
+ desc = channelwoodVars[i].description;
+ break;
+ }
+ }
+ break;
+ case kDniStack:
+ for (i = 0; i < ARRAYSIZE(dniVars); i++) {
+ if (dniVars[i].refNum == v) {
+ dniVars[i].storage = value;
+ desc = dniVars[i].description;
+ break;
+ }
+ }
+ break;
+ case kCreditsStack:
+ for (i = 0; i < ARRAYSIZE(creditsVars); i++) {
+ if (creditsVars[i].refNum == v) {
+ creditsVars[i].storage = value;
+ desc = creditsVars[i].description;
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (desc == NULL) {
+ for (i = 0; i < _unknown.size(); i++) {
+ if (_unknown[i].refNum == v) {
+ _unknown[i].storage = value;
+ desc = _unknown[i].description;
+ break;
+ }
+ }
+
+ if (desc == NULL) {
+ warning("MystVar::setVar(%d): Unknown variable reference", v);
+ _unknown.push_back(unknownVar);
+ desc = _unknown.back().description;
+ }
+ }
+
+ debugC(kDebugVariable, "MystVar::setVar(%d = %s): %d", v, desc, value);
+}
+
+uint16 MystVar::getVar(uint16 v) {
+ return this->saveGetVar(_vm->getCurStack(), v);
+}
+
+void MystVar::setVar(uint16 v, uint16 value) {
+ this->loadSetVar(_vm->getCurStack(), v, value);
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/myst_vars.h b/engines/mohawk/myst_vars.h
new file mode 100644
index 0000000000..065d8df2cb
--- /dev/null
+++ b/engines/mohawk/myst_vars.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "mohawk/myst.h"
+
+#ifndef MYST_VARS_H
+#define MYST_VARS_H
+
+namespace Mohawk {
+
+struct MystVarEntry {
+ uint16 refNum;
+ uint16 storage; // Used for Initial Value setting
+ const char *description;
+};
+
+class MystVar {
+public:
+ MystVar(MohawkEngine_Myst *vm);
+ ~MystVar();
+
+ // Only for use by Save/Load
+ // All other code should use getVar() / setVar()
+ void loadSetVar(uint16 stack, uint16 v, uint16 value);
+ uint16 saveGetVar(uint16 stack, uint16 v);
+
+ uint16 getVar(uint16 v);
+ void setVar(uint16 v, uint16 value);
+
+private:
+ MohawkEngine_Myst *_vm;
+
+ Common::Array<MystVarEntry> _unknown;
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/riven.cpp b/engines/mohawk/riven.cpp
new file mode 100644
index 0000000000..e2f581d75d
--- /dev/null
+++ b/engines/mohawk/riven.cpp
@@ -0,0 +1,586 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/config-manager.h"
+#include "common/events.h"
+#include "common/keyboard.h"
+
+#include "mohawk/graphics.h"
+#include "mohawk/riven.h"
+#include "mohawk/riven_external.h"
+#include "mohawk/riven_saveload.h"
+
+namespace Mohawk {
+
+MohawkEngine_Riven::MohawkEngine_Riven(OSystem *syst, const MohawkGameDescription *gamedesc) : MohawkEngine(syst, gamedesc) {
+ _showHotspots = false;
+ _cardData.hasData = false;
+ _gameOver = false;
+ _activatedSLST = false;
+ _extrasFile = NULL;
+
+ // Attempt to let game run from the CD's
+ // NOTE: assets2 contains higher quality audio than assets1
+ SearchMan.addSubDirectoryMatching(_gameDataDir, "all");
+ SearchMan.addSubDirectoryMatching(_gameDataDir, "data");
+ SearchMan.addSubDirectoryMatching(_gameDataDir, "exe");
+ SearchMan.addSubDirectoryMatching(_gameDataDir, "assets2");
+}
+
+MohawkEngine_Riven::~MohawkEngine_Riven() {
+ delete _gfx;
+ delete _console;
+ delete _externalScriptHandler;
+ delete _extrasFile;
+ delete _saveLoad;
+ delete[] _vars;
+ delete _loadDialog;
+ delete _optionsDialog;
+ _cardData.scripts.clear();
+}
+
+Common::Error MohawkEngine_Riven::run() {
+ MohawkEngine::run();
+
+ _gfx = new RivenGraphics(this);
+ _console = new RivenConsole(this);
+ _saveLoad = new RivenSaveLoad(this, _saveFileMan);
+ _externalScriptHandler = new RivenExternal(this);
+ _loadDialog = new GUI::SaveLoadChooser("Load Game:", "Load");
+ _loadDialog->setSaveMode(false);
+ _optionsDialog = new RivenOptionsDialog(this);
+
+ initVars();
+
+ // Open extras.mhk for common images (non-existant in the demo)
+ if (!(getFeatures() & GF_DEMO)) {
+ _extrasFile = new MohawkFile();
+ _extrasFile->open("extras.mhk");
+ }
+
+ // Start at main cursor
+ _gfx->changeCursor(kRivenMainCursor);
+
+ // Load game from launcher/command line if requested
+ if (ConfMan.hasKey("save_slot") && !(getFeatures() & GF_DEMO)) {
+ uint32 gameToLoad = ConfMan.getInt("save_slot");
+ Common::StringList savedGamesList = _saveLoad->generateSaveGameList();
+ if (gameToLoad > savedGamesList.size())
+ error ("Could not find saved game");
+ _saveLoad->loadGame(savedGamesList[gameToLoad]);
+ // HACK: The save_slot variable is saved to the disk! We don't want this!
+ ConfMan.removeKey("save_slot", ConfMan.getActiveDomainName());
+ ConfMan.flushToDisk();
+ } else { // Otherwise, start us off at aspit's card 1
+ changeToStack(aspit);
+ changeToCard(1);
+ }
+
+ Common::Event event;
+ while (!_gameOver) {
+ bool needsUpdate = _gfx->runScheduledWaterEffects();
+
+ while (_eventMan->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_MOUSEMOVE:
+ _mousePos = event.mouse;
+ checkHotspotChange();
+
+ // Check to show the inventory
+ if (_mousePos.y >= 392)
+ _gfx->showInventory();
+ else
+ _gfx->hideInventory();
+
+ needsUpdate = true;
+ break;
+ case Common::EVENT_LBUTTONDOWN:
+ if (_curHotspot >= 0) {
+ runHotspotScript(_curHotspot, kMouseDownScript);
+ //scheduleScript(_hotspots[_curHotspot].script, kMouseMovedPressedReleasedScript);
+ }
+ break;
+ case Common::EVENT_LBUTTONUP:
+ if (_curHotspot >= 0) {
+ runHotspotScript(_curHotspot, kMouseUpScript);
+ //scheduleScript(_hotspots[_curHotspot].script, kMouseMovedPressedReleasedScript);
+ } else
+ checkInventoryClick();
+ break;
+ case Common::EVENT_KEYDOWN:
+ switch (event.kbd.keycode) {
+ case Common::KEYCODE_d:
+ if (event.kbd.flags & Common::KBD_CTRL) {
+ _console->attach();
+ _console->onFrame();
+ }
+ break;
+ case Common::KEYCODE_SPACE:
+ pauseGame();
+ break;
+ case Common::KEYCODE_F4:
+ _showHotspots = !_showHotspots;
+ if (_showHotspots) {
+ for (uint16 i = 0; i < _hotspotCount; i++)
+ _gfx->drawRect(_hotspots[i].rect, _hotspots[i].enabled);
+ needsUpdate = true;
+ } else {
+ changeToCard();
+ }
+ break;
+ case Common::KEYCODE_F5:
+ runDialog(*_optionsDialog);
+ updateZipMode();
+ break;
+ case Common::KEYCODE_ESCAPE:
+ if (getFeatures() & GF_DEMO) {
+ if (_curStack != aspit)
+ changeToStack(aspit);
+ changeToCard(1);
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (_curHotspot >= 0) {
+ runHotspotScript(_curHotspot, kMouseInsideScript);
+ //scheduleScript(_hotspots[_curHotspot].script, kMouseMovedPressedReleasedScript);
+ }
+
+ if (shouldQuit()) {
+ if (_eventMan->shouldRTL() && (getFeatures() & GF_DEMO) && !(_curStack == aspit && _curCard == 12)) {
+ if (_curStack != aspit)
+ changeToStack(aspit);
+ changeToCard(12);
+ _eventMan->resetRTL();
+ continue;
+ }
+ return Common::kNoError;
+ }
+
+ // Update the screen if we need to
+ if (needsUpdate)
+ _system->updateScreen();
+
+ // Cut down on CPU usage
+ _system->delayMillis(10);
+ }
+
+ return Common::kNoError;
+}
+
+// Stack/Card-Related Functions
+
+void MohawkEngine_Riven::changeToStack(uint16 n) {
+ // The endings are in reverse order because of the way the 1.02 patch works.
+ // The only "Data3" file is j_Data3.mhk from that patch. Patch files have higher
+ // priorities over the regular files and are therefore loaded and checked first.
+ static const char *endings[] = { "_Data3.mhk", "_Data2.mhk", "_Data1.mhk", "_Data.mhk" };
+
+ // Don't change stack to the current stack (if the files are loaded)
+ if (_curStack == n && !_mhk.empty())
+ return;
+
+ _curStack = n;
+
+ // Clear the old stack files out
+ for (uint32 i = 0; i < _mhk.size(); i++)
+ delete _mhk[i];
+ _mhk.clear();
+
+ // Get the prefix character for the destination stack
+ char prefix = getStackName(_curStack)[0];
+
+ // Load any file that fits the patterns
+ for (int i = 0; i < ARRAYSIZE(endings); i++) {
+ Common::String filename = Common::String(prefix) + endings[i];
+ if (Common::File::exists(filename)) {
+ MohawkFile *mhk = new MohawkFile();
+ mhk->open(filename);
+ _mhk.push_back(mhk);
+ }
+ }
+
+ // Make sure we have loaded files
+ if (_mhk.empty())
+ error("Could not load stack %s", getStackName(_curStack).c_str());
+
+ // Stop any currently playing sounds and load the new sound file too
+ _sound->stopAllSLST();
+ _sound->loadRivenSounds(_curStack);
+}
+
+// Riven uses some hacks to change stacks for linking books
+// Otherwise, script command 27 changes stacks
+struct RivenSpecialChange {
+ byte startStack;
+ uint32 startCardRMAP;
+ byte targetStack;
+ uint32 targetCardRMAP;
+} rivenSpecialChange[] = {
+ { aspit, 0x1f04, ospit, 0x44ad }, // Trap Book
+ { bspit, 0x1c0e7, ospit, 0x2e76 }, // Dome Linking Book
+ { gspit, 0x111b1, ospit, 0x2e76 }, // Dome Linking Book
+ { jspit, 0x28a18, rspit, 0xf94 }, // Tay Linking Book
+ { jspit, 0x26228, ospit, 0x2e76 }, // Dome Linking Book
+ { ospit, 0x5f0d, pspit, 0x3bf0 }, // Return from 233rd Age
+ { ospit, 0x470a, jspit, 0x1508e }, // Return from 233rd Age
+ { ospit, 0x5c52, gspit, 0x10bea }, // Return from 233rd Age
+ { ospit, 0x5d68, bspit, 0x1adfd }, // Return from 233rd Age
+ { ospit, 0x5e49, tspit, 0xe78 }, // Return from 233rd Age
+ { pspit, 0x4108, ospit, 0x2e76 }, // Dome Linking Book
+ { rspit, 0x32d8, jspit, 0x1c474 }, // Return from Tay
+ { tspit, 0x21b69, ospit, 0x2e76 } // Dome Linking Book
+};
+
+void MohawkEngine_Riven::changeToCard(uint16 n) {
+ bool refreshMode = (n == 0);
+
+ // While this could be run without harm, it doesn't need to be. This should add a speed boost.
+ if (!refreshMode) {
+ debug (1, "Changing to card %d", n);
+ _curCard = n;
+
+ if (!(getFeatures() & GF_DEMO)) {
+ for (byte i = 0; i < 13; i++)
+ if (_curStack == rivenSpecialChange[i].startStack && _curCard == matchRMAPToCard(rivenSpecialChange[i].startCardRMAP)) {
+ changeToStack(rivenSpecialChange[i].targetStack);
+ _curCard = matchRMAPToCard(rivenSpecialChange[i].targetCardRMAP);
+ }
+ }
+
+ if (_cardData.hasData)
+ runCardScript(kCardLeaveScript);
+
+ loadCard(_curCard);
+ }
+
+ // We need to reload hotspots when refreshing, however
+ loadHotspots(_curCard);
+
+ _gfx->_updatesEnabled = true;
+ _gfx->clearWaterEffects();
+ _gfx->_activatedPLSTs.clear();
+ _video->_mlstRecords.clear();
+ _gfx->drawPLST(1);
+ _activatedSLST = false;
+
+ runCardScript(kCardLoadScript);
+ _gfx->updateScreen();
+ runCardScript(kCardOpenScript);
+
+ // Activate the first sound list if none have been activated
+ if (!_activatedSLST)
+ _sound->playSLST(1, _curCard);
+
+ if (_showHotspots) {
+ for (uint16 i = 0; i < _hotspotCount; i++)
+ _gfx->drawRect(_hotspots[i].rect, _hotspots[i].enabled);
+ }
+
+ // Now we need to redraw the cursor if necessary and handle mouse over scripts
+ _curHotspot = -1;
+ checkHotspotChange();
+}
+
+void MohawkEngine_Riven::loadCard(uint16 id) {
+ // NOTE: Do not clear the card scripts because it may delete a currently running script!
+
+ Common::SeekableReadStream* inStream = getRawData(ID_CARD, id);
+
+ _cardData.name = inStream->readSint16BE();
+ _cardData.zipModePlace = inStream->readUint16BE();
+ _cardData.scripts = RivenScript::readScripts(this, inStream);
+ _cardData.hasData = true;
+
+ delete inStream;
+
+ if (_cardData.zipModePlace) {
+ Common::String cardName = getName(CardNames, _cardData.name);
+ if (cardName.empty())
+ return;
+ ZipMode zip;
+ zip.name = cardName;
+ zip.id = id;
+ if (!(Common::find(_zipModeData.begin(), _zipModeData.end(), zip) != _zipModeData.end()))
+ _zipModeData.push_back(zip);
+ }
+}
+
+void MohawkEngine_Riven::loadHotspots(uint16 id) {
+ // NOTE: Do not clear the hotspots because it may delete a currently running script!
+
+ Common::SeekableReadStream* inStream = getRawData(ID_HSPT, id);
+
+ _hotspotCount = inStream->readUint16BE();
+ _hotspots = new RivenHotspot[_hotspotCount];
+
+
+ for (uint16 i = 0; i < _hotspotCount; i++) {
+ _hotspots[i].enabled = true;
+
+ _hotspots[i].blstID = inStream->readUint16BE();
+ _hotspots[i].name_resource = inStream->readSint16BE();
+
+ int16 left = inStream->readSint16BE();
+ int16 top = inStream->readSint16BE();
+ int16 right = inStream->readSint16BE();
+ int16 bottom = inStream->readSint16BE();
+
+ // Riven has some weird hotspots, disable them here
+ if (left >= right || top >= bottom) {
+ left = top = right = bottom = 0;
+ _hotspots[i].enabled = 0;
+ }
+
+ _hotspots[i].rect = Common::Rect(left, top, right, bottom);
+
+ _hotspots[i].u0 = inStream->readUint16BE();
+
+ if (_hotspots[i].u0 != 0)
+ warning("Hotspot %d u0 non-zero", i);
+
+ _hotspots[i].mouse_cursor = inStream->readUint16BE();
+ _hotspots[i].index = inStream->readUint16BE();
+ _hotspots[i].u1 = inStream->readSint16BE();
+
+ if (_hotspots[i].u1 != -1)
+ warning("Hotspot %d u1 not -1", i);
+
+ _hotspots[i].zipModeHotspot = inStream->readUint16BE();
+
+ // Read in the scripts now
+ _hotspots[i].scripts = RivenScript::readScripts(this, inStream);
+ }
+
+ delete inStream;
+ updateZipMode();
+}
+
+void MohawkEngine_Riven::updateZipMode() {
+ // Check if a zip mode hotspot is enabled by checking the name/id against the ZIPS records.
+
+ for (uint32 i = 0; i < _hotspotCount; i++) {
+ if (_hotspots[i].zipModeHotspot) {
+ if (*matchVarToString("azip") != 0) {
+ // Check if a zip mode hotspot is enabled by checking the name/id against the ZIPS records.
+ Common::String hotspotName = getName(HotspotNames, _hotspots[i].name_resource);
+
+ bool foundMatch = false;
+
+ if (!hotspotName.empty())
+ for (uint16 j = 0; j < _zipModeData.size(); j++)
+ if (_zipModeData[j].name == hotspotName) {
+ foundMatch = true;
+ break;
+ }
+
+ _hotspots[i].enabled = foundMatch;
+ } else // Disable the hotspot if zip mode is disabled
+ _hotspots[i].enabled = false;
+ }
+ }
+}
+
+void MohawkEngine_Riven::checkHotspotChange() {
+ uint16 hotspotIndex = 0;
+ bool foundHotspot = false;
+ for (uint16 i = 0; i < _hotspotCount; i++)
+ if (_hotspots[i].enabled && _hotspots[i].rect.contains(_mousePos)) {
+ foundHotspot = true;
+ hotspotIndex = i;
+ }
+
+ if (foundHotspot) {
+ if (_curHotspot != hotspotIndex) {
+ _curHotspot = hotspotIndex;
+ _gfx->changeCursor(_hotspots[_curHotspot].mouse_cursor);
+ }
+ } else {
+ _curHotspot = -1;
+ _gfx->changeCursor(kRivenMainCursor);
+ }
+}
+
+Common::String MohawkEngine_Riven::getHotspotName(uint16 hotspot) {
+ assert(hotspot < _hotspotCount);
+
+ if (_hotspots[hotspot].name_resource < 0)
+ return Common::String::emptyString;
+
+ return getName(HotspotNames, _hotspots[hotspot].name_resource);
+}
+
+void MohawkEngine_Riven::checkInventoryClick() {
+ // Don't even bother. We're not in the inventory portion of the screen.
+ if (_mousePos.y < 392)
+ return;
+
+ // No inventory in the demo or opening screens.
+ if (getFeatures() & GF_DEMO || _curStack == aspit)
+ return;
+
+ // Set the return stack/card id's.
+ *matchVarToString("returnstackid") = _curStack;
+ *matchVarToString("returncardid") = _curCard;
+
+ // See RivenGraphics::showInventory() for an explanation
+ // of why only this variable is used.
+ bool hasCathBook = *matchVarToString("acathbook") != 0;
+
+ // Go to the book if a hotspot contains the mouse
+ if (!hasCathBook) {
+ if (atrusJournalRectSolo.contains(_mousePos)) {
+ _gfx->hideInventory();
+ changeToStack(aspit);
+ changeToCard(5);
+ }
+ } else {
+ if (atrusJournalRect.contains(_mousePos)) {
+ _gfx->hideInventory();
+ changeToStack(aspit);
+ changeToCard(5);
+ } else if (cathJournalRect.contains(_mousePos)) {
+ _gfx->hideInventory();
+ changeToStack(aspit);
+ changeToCard(6);
+ } else if (trapBookRect.contains(_mousePos)) {
+ _gfx->hideInventory();
+ changeToStack(aspit);
+ changeToCard(7);
+ }
+ }
+}
+
+Common::SeekableReadStream *MohawkEngine_Riven::getExtrasResource(uint32 tag, uint16 id) {
+ return _extrasFile->getRawData(tag, id);
+}
+
+Common::String MohawkEngine_Riven::getName(uint16 nameResource, uint16 nameID) {
+ Common::SeekableReadStream* nameStream = getRawData(ID_NAME, nameResource);
+ uint16 fieldCount = nameStream->readUint16BE();
+ uint16* stringOffsets = new uint16[fieldCount];
+ Common::String name = Common::String::emptyString;
+ char c;
+
+ if (nameID < fieldCount) {
+ for (uint16 i = 0; i < fieldCount; i++)
+ stringOffsets[i] = nameStream->readUint16BE();
+ for (uint16 i = 0; i < fieldCount; i++)
+ nameStream->readUint16BE(); // Skip unknown values
+
+ nameStream->seek(stringOffsets[nameID], SEEK_CUR);
+ c = (char)nameStream->readByte();
+
+ while (c) {
+ name += c;
+ c = (char)nameStream->readByte();
+ }
+ }
+
+ delete nameStream;
+ delete [] stringOffsets;
+ return name;
+}
+
+uint16 MohawkEngine_Riven::matchRMAPToCard(uint32 rmapCode) {
+ uint16 index = 0;
+ Common::SeekableReadStream *rmapStream = getRawData(ID_RMAP, 1);
+
+ for (uint16 i = 1; rmapStream->pos() < rmapStream->size(); i++) {
+ uint32 code = rmapStream->readUint32BE();
+ if (code == rmapCode)
+ index = i;
+ }
+
+ delete rmapStream;
+
+ if (!index)
+ error ("Could not match RMAP code %08x", rmapCode);
+
+ return index - 1;
+}
+
+void MohawkEngine_Riven::runCardScript(uint16 scriptType) {
+ assert(_cardData.hasData);
+ for (uint16 i = 0; i < _cardData.scripts.size(); i++)
+ if (_cardData.scripts[i]->getScriptType() == scriptType) {
+ _cardData.scripts[i]->runScript();
+ break;
+ }
+}
+
+void MohawkEngine_Riven::runHotspotScript(uint16 hotspot, uint16 scriptType) {
+ assert(hotspot < _hotspotCount);
+ for (uint16 i = 0; i < _hotspots[hotspot].scripts.size(); i++)
+ if (_hotspots[hotspot].scripts[i]->getScriptType() == scriptType) {
+ _hotspots[hotspot].scripts[i]->runScript();
+ break;
+ }
+}
+
+void MohawkEngine_Riven::runLoadDialog() {
+ runDialog(*_loadDialog);
+}
+
+Common::Error MohawkEngine_Riven::loadGameState(int slot) {
+ return _saveLoad->loadGame(_saveLoad->generateSaveGameList()[slot]) ? Common::kNoError : Common::kUnknownError;
+}
+
+Common::Error MohawkEngine_Riven::saveGameState(int slot, const char *desc) {
+ Common::StringList saveList = _saveLoad->generateSaveGameList();
+
+ if ((uint)slot < saveList.size())
+ _saveLoad->deleteSave(saveList[slot]);
+
+ return _saveLoad->saveGame(Common::String(desc)) ? Common::kNoError : Common::kUnknownError;
+}
+
+static const char *rivenStackNames[] = {
+ "aspit",
+ "bspit",
+ "gspit",
+ "jspit",
+ "ospit",
+ "pspit",
+ "rspit",
+ "tspit"
+};
+
+Common::String MohawkEngine_Riven::getStackName(uint16 stack) {
+ return Common::String(rivenStackNames[stack]);
+}
+
+bool ZipMode::operator== (const ZipMode &z) const {
+ return z.name == name && z.id == id;
+}
+
+}
diff --git a/engines/mohawk/riven.h b/engines/mohawk/riven.h
new file mode 100644
index 0000000000..d8ef5c281f
--- /dev/null
+++ b/engines/mohawk/riven.h
@@ -0,0 +1,180 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MOHAWK_RIVEN_H
+#define MOHAWK_RIVEN_H
+
+#include "mohawk/mohawk.h"
+#include "mohawk/riven_scripts.h"
+
+#include "gui/saveload.h"
+
+namespace Mohawk {
+
+struct MohawkGameDescription;
+class MohawkFile;
+class RivenGraphics;
+class RivenExternal;
+class RivenConsole;
+class RivenSaveLoad;
+
+#define RIVEN_STACKS 8
+
+// Riven Stack Types
+enum {
+ aspit = 0, // Main Menu, Books, Setup
+ bspit = 1, // Book-Making Island
+ gspit = 2, // Garden Island
+ jspit = 3, // Jungle Island
+ ospit = 4, // 233rd Age (Gehn's Office)
+ pspit = 5, // Prison Island
+ rspit = 6, // Rebel Age (Tay)
+ tspit = 7 // Temple Island
+};
+
+// NAME Resource ID's
+enum {
+ CardNames = 1,
+ HotspotNames = 2,
+ ExternalCommandNames = 3,
+ VariableNames = 4,
+ StackNames = 5
+};
+
+// Rects for the inventory object positions
+static const Common::Rect atrusJournalRectSolo = Common::Rect(295, 402, 313, 426);
+static const Common::Rect atrusJournalRect = Common::Rect(222, 402, 240, 426);
+static const Common::Rect cathJournalRect = Common::Rect(291, 408, 311, 419);
+static const Common::Rect trapBookRect = Common::Rect(363, 396, 386, 432);
+
+struct RivenHotspot {
+ uint16 blstID;
+ int16 name_resource;
+ Common::Rect rect;
+ uint16 u0;
+ uint16 mouse_cursor;
+ uint16 index;
+ int16 u1;
+ int16 zipModeHotspot;
+ RivenScriptList scripts;
+
+ bool enabled;
+};
+
+struct Card {
+ int16 name;
+ uint16 zipModePlace;
+ bool hasData;
+ RivenScriptList scripts;
+};
+
+struct ZipMode {
+ Common::String name;
+ uint16 id;
+ bool operator== (const ZipMode& z) const;
+};
+
+class MohawkEngine_Riven : public MohawkEngine {
+protected:
+ Common::Error run();
+
+public:
+ MohawkEngine_Riven(OSystem *syst, const MohawkGameDescription *gamedesc);
+ virtual ~MohawkEngine_Riven();
+
+ RivenGraphics *_gfx;
+ RivenExternal *_externalScriptHandler;
+
+ Card _cardData;
+ bool _gameOver;
+
+ GUI::Debugger *getDebugger() { return _console; }
+
+ bool canLoadGameStateCurrently() { return true; }
+ bool canSaveGameStateCurrently() { return true; }
+ Common::Error loadGameState(int slot);
+ Common::Error saveGameState(int slot, const char *desc);
+ bool hasFeature(EngineFeature f) const;
+
+private:
+ MohawkFile *_extrasFile; // We need a separate handle for the extra data
+ RivenConsole *_console;
+ RivenSaveLoad *_saveLoad;
+ GUI::SaveLoadChooser *_loadDialog;
+ RivenOptionsDialog *_optionsDialog;
+
+ // Stack/Card-related functions and variables
+ uint16 _curCard;
+ uint16 _curStack;
+ void loadCard(uint16);
+
+ // Hotspot related functions and variables
+ uint16 _hotspotCount;
+ void loadHotspots(uint16);
+ void checkHotspotChange();
+ void checkInventoryClick();
+ bool _showHotspots;
+ void updateZipMode();
+
+ // Variables
+ uint32 *_vars;
+ uint32 _varCount;
+
+public:
+ Common::SeekableReadStream *getExtrasResource(uint32 tag, uint16 id);
+ bool _activatedSLST;
+ void runLoadDialog();
+
+ void changeToCard(uint16 = 0);
+ void changeToStack(uint16);
+ Common::String getName(uint16 nameResource, uint16 nameID);
+ Common::String getStackName(uint16 stack);
+ void runCardScript(uint16 scriptType);
+ void runUpdateScreenScript() { runCardScript(kCardUpdateScript); }
+ uint16 getCurCard() { return _curCard; }
+ uint16 getCurStack() { return _curStack; }
+ uint16 matchRMAPToCard(uint32);
+
+ Common::Point _mousePos;
+ RivenHotspot *_hotspots;
+ int32 _curHotspot;
+ Common::Array<ZipMode> _zipModeData;
+ uint16 getHotspotCount() { return _hotspotCount; }
+ void runHotspotScript(uint16 hotspot, uint16 scriptType);
+ int32 getCurHotspot() { return _curHotspot; }
+ Common::String getHotspotName(uint16 hotspot);
+
+ void initVars();
+ uint32 getVarCount() { return _varCount; }
+ uint32 getGlobalVar(uint32 index);
+ Common::String getGlobalVarName(uint32 index);
+ uint32 *getLocalVar(uint32 index);
+ uint32 *matchVarToString(Common::String varName);
+ uint32 *matchVarToString(const char *varName);
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/riven_cursors.h b/engines/mohawk/riven_cursors.h
new file mode 100644
index 0000000000..4120aca41b
--- /dev/null
+++ b/engines/mohawk/riven_cursors.h
@@ -0,0 +1,404 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+namespace Mohawk {
+
+//////////////////////////////////////////////
+// Cursors and Cursor Palettes
+//////////////////////////////////////////////
+
+////////////////////////////////////////
+// Zip Mode Cursor (16x16):
+// Shown when a zip mode spot is active
+//
+// 0 = Transparent
+// 1 = Black (0x000000)
+// 2 = Yellow (0xDCFF00)
+////////////////////////////////////////
+static const byte zipModeCursor[] = {
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 1, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 2, 1, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 2, 1, 1, 2, 2, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 2, 2, 1, 2, 2, 2, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 2, 2, 2, 1, 2, 2, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 2, 2, 1, 1, 2, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 1, 2, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
+};
+
+
+////////////////////////////////////////
+// Zip Mode Cursor Palette:
+// Palette For The Zip Mode Cursor
+////////////////////////////////////////
+static const byte zipModeCursorPalette[] = {
+ 0x00, 0x00, 0x00, 0x00, // Black
+ 0xDC, 0xFF, 0x00, 0x00, // Yellow
+};
+
+
+////////////////////////////////////////
+// Hand Over Object Cursor (16x16):
+// Shown when over a hotspot that's interactive
+//
+// 0 = Transparent
+// 1 = Black (0x000000)
+// 2 = Light Peach (0xEDCD96)
+// 3 = Brown (0x8A672F)
+// 4 = Dark Peach (0xE89A62)
+////////////////////////////////////////
+static const byte objectHandCursor[] = {
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 0, 1, 2, 3, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 1, 2, 3, 1, 1, 2, 3, 1, 2, 3, 1, 0, 0, 0,
+ 0, 0, 1, 2, 3, 1, 1, 4, 3, 1, 2, 3, 1, 0, 1, 0,
+ 0, 0, 0, 1, 2, 3, 1, 2, 3, 1, 4, 3, 1, 1, 2, 1,
+ 0, 0, 0, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1,
+ 0, 1, 1, 0, 1, 2, 2, 2, 2, 2, 2, 3, 1, 2, 3, 1,
+ 1, 2, 2, 1, 1, 2, 2, 2, 4, 2, 4, 2, 2, 4, 2, 1,
+ 1, 3, 4, 2, 1, 2, 4, 2, 2, 2, 2, 2, 4, 4, 1, 0,
+ 0, 1, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 3, 1, 0,
+ 0, 0, 1, 3, 2, 2, 2, 2, 2, 2, 2, 4, 4, 3, 1, 0,
+ 0, 0, 1, 3, 2, 2, 2, 2, 2, 2, 2, 4, 3, 1, 0, 0,
+ 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 1, 0, 0,
+ 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 4, 3, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 4, 3, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 4, 3, 1, 0, 0, 0
+};
+
+
+////////////////////////////////////////
+// Grabbing Hand Cursor (13x13):
+// Shown when interacting with an object
+//
+// 0 = Transparent
+// 1 = Black (0x000000)
+// 2 = Light Peach (0xEDCD96)
+// 3 = Brown (0x8A672F)
+// 4 = Dark Peach (0xE89A62)
+////////////////////////////////////////
+static const byte grabbingHandCursor[] = {
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 1, 1, 2, 3, 1, 1, 1, 0, 0, 0,
+ 0, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 1, 0,
+ 0, 1, 2, 2, 2, 2, 2, 2, 2, 3, 1, 2, 1,
+ 0, 1, 1, 2, 2, 2, 4, 2, 4, 2, 2, 4, 1,
+ 1, 2, 1, 2, 4, 2, 2, 2, 2, 2, 4, 4, 1,
+ 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 3, 1,
+ 1, 3, 2, 2, 2, 2, 2, 2, 2, 4, 4, 3, 1,
+ 1, 3, 2, 2, 2, 2, 2, 2, 2, 4, 3, 1, 0,
+ 0, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 1, 0,
+ 0, 0, 1, 2, 2, 2, 2, 2, 4, 3, 1, 0, 0,
+ 0, 0, 0, 1, 2, 2, 2, 2, 4, 3, 1, 0, 0,
+ 0, 0, 0, 1, 2, 2, 2, 2, 4, 3, 1, 0, 0
+};
+
+
+////////////////////////////////////////
+// Standard Hand Cursor (15x16):
+// Standard Cursor
+//
+// 0 = Transparent
+// 1 = Black (0x000000)
+// 2 = Light Peach (0xEDCD96)
+// 3 = Brown (0x8A672F)
+// 4 = Dark Peach (0xE89A62)
+////////////////////////////////////////
+static const byte standardHandCursor[] = {
+ 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 4, 4, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 2, 3, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 2, 4, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 2, 4, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 2, 4, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 1, 2, 4, 1, 1, 1, 1, 1, 0, 0,
+ 1, 4, 2, 1, 0, 1, 2, 4, 1, 4, 1, 4, 1, 1, 1,
+ 0, 1, 3, 2, 1, 1, 2, 4, 1, 4, 1, 4, 1, 4, 1,
+ 0, 0, 1, 4, 2, 1, 2, 2, 4, 2, 4, 2, 1, 4, 1,
+ 0, 0, 1, 4, 2, 1, 2, 2, 2, 2, 2, 2, 2, 3, 1,
+ 0, 0, 0, 1, 4, 2, 2, 2, 2, 2, 2, 2, 4, 3, 1,
+ 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 4, 3, 1,
+ 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 3, 1, 0,
+ 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 4, 3, 1, 0,
+ 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 4, 3, 1, 0
+};
+
+
+////////////////////////////////////////
+// Pointing Left Cursor (15x13):
+// Cursor When Over A Hotspot That Allows You To Move Left
+//
+// 0 = Transparent
+// 1 = Black (0x000000)
+// 2 = Light Peach (0xEDCD96)
+// 3 = Brown (0x8A672F)
+// 4 = Dark Peach (0xE89A62)
+////////////////////////////////////////
+static const byte pointingLeftCursor[] = {
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 3, 2, 2, 2, 1, 1, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 3, 1, 1,
+ 1, 4, 2, 2, 2, 2, 1, 2, 3, 2, 2, 2, 4, 4, 4,
+ 1, 4, 4, 4, 4, 4, 1, 2, 1, 3, 4, 2, 2, 2, 2,
+ 0, 1, 1, 1, 1, 1, 1, 2, 1, 3, 3, 4, 2, 2, 2,
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 3, 3, 3, 4, 4, 2,
+ 0, 0, 0, 0, 0, 1, 2, 2, 2, 4, 1, 3, 4, 2, 2,
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 3, 4, 2, 2,
+ 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 1, 4, 2, 4,
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 4, 4, 1,
+ 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0
+};
+
+
+////////////////////////////////////////
+// Pointing Right Cursor (15x13):
+// Cursor When Over A Hotspot That Allows You To Move Right
+//
+// 0 = Transparent
+// 1 = Black (0x000000)
+// 2 = Light Peach (0xEDCD96)
+// 3 = Brown (0x8A672F)
+// 4 = Dark Peach (0xE89A62)
+////////////////////////////////////////
+static const byte pointingRightCursor[] = {
+ 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 1, 2, 2, 2, 3, 1, 0, 0, 0, 0, 0, 0,
+ 1, 1, 3, 4, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0,
+ 4, 4, 4, 2, 2, 2, 3, 2, 1, 4, 4, 4, 4, 4, 1,
+ 2, 2, 2, 2, 4, 3, 1, 2, 1, 2, 2, 2, 2, 4, 1,
+ 2, 2, 2, 4, 3, 3, 1, 2, 1, 1, 1, 1, 1, 1, 0,
+ 2, 4, 4, 3, 3, 3, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 2, 2, 4, 3, 1, 4, 2, 2, 2, 1, 0, 0, 0, 0, 0,
+ 2, 2, 4, 3, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 4, 2, 4, 1, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0,
+ 1, 4, 4, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0
+};
+
+
+////////////////////////////////////////
+// Pointing Down Cursor (Palm Up)(13x16):
+// Cursor When Over A Hotspot That Allows You To Move Down
+//
+// 0 = Transparent
+// 1 = Black (0x000000)
+// 2 = Light Peach (0xEDCD96)
+// 3 = Brown (0x8A672F)
+// 4 = Dark Peach (0xE89A62)
+////////////////////////////////////////
+static const byte pointingDownCursorPalmUp[] = {
+ 0, 0, 1, 4, 2, 2, 2, 2, 2, 4, 1, 0, 0,
+ 0, 0, 1, 4, 2, 2, 4, 2, 2, 2, 4, 1, 0,
+ 0, 1, 3, 4, 2, 2, 4, 4, 4, 4, 4, 1, 0,
+ 0, 1, 4, 2, 2, 4, 3, 3, 3, 1, 1, 1, 1,
+ 1, 2, 2, 2, 4, 3, 3, 1, 1, 2, 1, 2, 1,
+ 1, 2, 2, 2, 3, 3, 3, 4, 1, 2, 1, 2, 1,
+ 1, 2, 2, 3, 1, 1, 1, 2, 1, 2, 1, 2, 1,
+ 1, 3, 2, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1,
+ 0, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1,
+ 0, 0, 1, 2, 4, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 0, 1, 2, 4, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 2, 4, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 2, 4, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 2, 4, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+
+////////////////////////////////////////
+// Pointing Up Cursor (Palm Up)(13x16):
+// Cursor When Over A Hotspot That Allows You To Move Up
+//
+// 0 = Transparent
+// 1 = Black (0x000000)
+// 2 = Light Peach (0xEDCD96)
+// 3 = Brown (0x8A672F)
+// 4 = Dark Peach (0xE89A62)
+////////////////////////////////////////
+static const byte pointingUpCursorPalmUp[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 4, 4, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 1, 0, 0,
+ 0, 0, 1, 1, 1, 1, 1, 1, 2, 4, 1, 0, 0,
+ 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 0,
+ 1, 2, 1, 2, 1, 2, 1, 2, 2, 2, 2, 3, 1,
+ 1, 2, 1, 2, 1, 2, 1, 1, 1, 3, 2, 2, 1,
+ 1, 2, 1, 2, 1, 4, 3, 3, 3, 2, 2, 2, 1,
+ 1, 2, 1, 2, 1, 1, 3, 3, 4, 2, 2, 2, 1,
+ 1, 1, 1, 1, 3, 3, 3, 4, 2, 2, 4, 1, 0,
+ 0, 1, 4, 4, 4, 4, 4, 2, 2, 4, 3, 1, 0,
+ 0, 1, 4, 2, 2, 2, 4, 2, 2, 4, 1, 0, 0,
+ 0, 0, 1, 4, 2, 2, 2, 2, 2, 4, 1, 0, 0
+};
+
+
+////////////////////////////////////////
+// Pointing Left Cursor (Bent)(15x13):
+// Cursor When Over A Hotspot That Allows You To Turn Left 180 Degrees
+//
+// 0 = Transparent
+// 1 = Black (0x000000)
+// 2 = Light Peach (0xEDCD96)
+// 3 = Brown (0x8A672F)
+// 4 = Dark Peach (0xE89A62)
+////////////////////////////////////////
+static const byte pointingLeftCursorBent[] = {
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 3, 2, 2, 2, 1, 1, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 3, 1, 1,
+ 1, 3, 2, 4, 4, 2, 1, 2, 3, 3, 2, 2, 4, 4, 4,
+ 1, 2, 4, 3, 3, 4, 1, 2, 1, 3, 4, 2, 2, 2, 2,
+ 1, 4, 4, 1, 1, 1, 1, 2, 1, 1, 3, 4, 2, 2, 2,
+ 1, 1, 1, 0, 0, 1, 1, 1, 1, 3, 3, 3, 4, 4, 2,
+ 0, 0, 0, 0, 0, 1, 2, 2, 2, 4, 1, 3, 4, 3, 2,
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 3, 4, 2, 2,
+ 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 1, 4, 2, 4,
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 4, 4, 1,
+ 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0
+};
+
+
+////////////////////////////////////////
+// Pointing Right Cursor (Bent)(15x13):
+// Cursor When Over A Hotspot That Allows You To Turn Right 180 Degrees
+//
+// 0 = Transparent
+// 1 = Black (0x000000)
+// 2 = Light Peach (0xEDCD96)
+// 3 = Brown (0x8A672F)
+// 4 = Dark Peach (0xE89A62)
+////////////////////////////////////////
+static const byte pointingRightCursorBent[] = {
+ 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 1, 2, 2, 2, 3, 1, 0, 0, 0, 0, 0, 0,
+ 1, 1, 3, 4, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0,
+ 4, 4, 4, 2, 2, 3, 3, 2, 1, 2, 4, 4, 2, 3, 1,
+ 2, 2, 2, 2, 4, 3, 1, 2, 1, 4, 3, 3, 4, 2, 1,
+ 2, 2, 2, 4, 3, 1, 1, 2, 1, 1, 1, 1, 4, 4, 1,
+ 2, 4, 4, 3, 3, 3, 1, 1, 1, 1, 0, 0, 1, 1, 1,
+ 2, 3, 4, 3, 1, 4, 2, 2, 2, 1, 0, 0, 0, 0, 0,
+ 2, 2, 4, 3, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 4, 2, 4, 1, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0,
+ 1, 4, 4, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0
+};
+
+
+////////////////////////////////////////
+// Pointing Down Cursor (Palm Down)(15x16):
+// Similar to Standard Cursor
+//
+// 0 = Transparent
+// 1 = Black (0x000000)
+// 2 = Light Peach (0xEDCD96)
+// 3 = Brown (0x8A672F)
+// 4 = Dark Peach (0xE89A62)
+////////////////////////////////////////
+static const byte pointingDownCursorPalmDown[] = {
+ 0, 1, 3, 4, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0,
+ 0, 1, 3, 4, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0,
+ 0, 1, 3, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0,
+ 1, 3, 4, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0,
+ 1, 3, 4, 2, 2, 2, 2, 2, 2, 2, 4, 1, 0, 0, 0,
+ 1, 3, 2, 3, 2, 2, 2, 2, 2, 1, 2, 4, 1, 0, 0,
+ 1, 4, 1, 2, 2, 3, 2, 3, 2, 1, 2, 4, 1, 0, 0,
+ 1, 4, 1, 4, 1, 4, 1, 4, 4, 1, 1, 2, 3, 1, 0,
+ 0, 1, 1, 4, 1, 4, 1, 4, 2, 1, 0, 1, 2, 4, 1,
+ 0, 0, 1, 1, 1, 1, 1, 4, 2, 1, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 1, 4, 4, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 4, 2, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 4, 2, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 3, 2, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 4, 4, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0
+};
+
+////////////////////////////////////////
+// Hand Cursor Palette:
+// Palette For All Hand Cursors
+////////////////////////////////////////
+static const byte handCursorPalette[] = {
+ 0x00, 0x00, 0x00, 0x00, // Black
+ 0xED, 0xCD, 0x96, 0x00, // Light Peach
+ 0x8A, 0x67, 0x2F, 0x00, // Brown
+ 0xE8, 0x9A, 0x62, 0x00 // Dark Peach
+};
+
+
+////////////////////////////////////////
+// Pellet Cursor (8x8):
+// Cursor When Using The Pellet In The Frog Trap
+//
+// 0 = Transparent
+// 1 = Light Olive Green (0x5D6730)
+// 2 = Maroon (0x5E3333)
+// 3 = Light Gray (0x555555)
+// 4 = Medium Gray (0x444444)
+// 5 = Dark Gray (0x333333)
+// 6 = Dark Green (0x2D3300)
+// 7 = Darkest Gray (0x222222)
+////////////////////////////////////////
+static const byte pelletCursor[] = {
+ 0, 0, 1, 1, 2, 3, 0, 0,
+ 0, 2, 1, 4, 1, 2, 5, 0,
+ 4, 1, 4, 1, 2, 1, 5, 4,
+ 4, 2, 1, 2, 1, 1, 2, 6,
+ 6, 4, 2, 1, 4, 4, 1, 5,
+ 5, 6, 5, 2, 1, 2, 4, 4,
+ 0, 7, 5, 5, 4, 2, 5, 0,
+ 0, 0, 5, 6, 6, 5, 0, 0
+};
+
+////////////////////////////////////////
+// Pellet Cursor Palette:
+// Palette For The Pellet Cursor
+////////////////////////////////////////
+static const byte pelletCursorPalette[] = {
+ 0x5D, 0x67, 0x30, 0x00,
+ 0x5E, 0x33, 0x33, 0x00,
+ 0x55, 0x55, 0x55, 0x00,
+ 0x44, 0x44, 0x44, 0x00,
+ 0x33, 0x33, 0x33, 0x00,
+ 0x2D, 0x33, 0x00, 0x00,
+ 0x22, 0x22, 0x22, 0x00
+};
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/riven_external.cpp b/engines/mohawk/riven_external.cpp
new file mode 100644
index 0000000000..647f3a35f6
--- /dev/null
+++ b/engines/mohawk/riven_external.cpp
@@ -0,0 +1,1467 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "mohawk/graphics.h"
+#include "mohawk/riven.h"
+#include "mohawk/riven_external.h"
+
+#include "common/EventRecorder.h"
+#include "gui/message.h"
+
+namespace Mohawk {
+
+RivenExternal::RivenExternal(MohawkEngine_Riven *vm) : _vm(vm) {
+ setupCommands();
+ _rnd = new Common::RandomSource();
+ g_eventRec.registerRandomSource(*_rnd, "riven");
+}
+
+RivenExternal::~RivenExternal() {
+ delete _rnd;
+
+ for (uint32 i = 0; i < _externalCommands.size(); i++)
+ delete _externalCommands[i];
+
+ _externalCommands.clear();
+}
+
+void RivenExternal::setupCommands() {
+ // aspit (Main Menu, Books, Setup) external commands
+ COMMAND(xastartupbtnhide);
+ COMMAND(xasetupcomplete);
+ COMMAND(xaatrusopenbook);
+ COMMAND(xaatrusbookback);
+ COMMAND(xaatrusbookprevpage);
+ COMMAND(xaatrusbooknextpage);
+ COMMAND(xacathopenbook);
+ COMMAND(xacathbookback);
+ COMMAND(xacathbookprevpage);
+ COMMAND(xacathbooknextpage);
+ COMMAND(xtrapbookback);
+ COMMAND(xatrapbookclose);
+ COMMAND(xatrapbookopen);
+ COMMAND(xarestoregame);
+ COMMAND(xadisablemenureturn);
+ COMMAND(xaenablemenureturn);
+ COMMAND(xalaunchbrowser);
+
+ // bspit (Bookmaking Island) external commands
+ COMMAND(xblabopenbook);
+ COMMAND(xblabbookprevpage);
+ COMMAND(xblabbooknextpage);
+ COMMAND(xsoundplug);
+ COMMAND(xbchangeboiler);
+ COMMAND(xbupdateboiler);
+ COMMAND(xbsettrap);
+ COMMAND(xbcheckcatch);
+ COMMAND(xbait);
+ COMMAND(xbfreeytram);
+ COMMAND(xbaitplate);
+ COMMAND(xbisland190_opencard);
+ COMMAND(xbisland190_resetsliders);
+ COMMAND(xbisland190_slidermd);
+ COMMAND(xbisland190_slidermw);
+ COMMAND(xbscpbtn);
+ COMMAND(xbisland_domecheck);
+ COMMAND(xvalvecontrol);
+ COMMAND(xbchipper);
+
+ // gspit (Garden Island) external commands
+ COMMAND(xgresetpins);
+ COMMAND(xgrotatepins);
+ COMMAND(xgpincontrols);
+ COMMAND(xgisland25_opencard);
+ COMMAND(xgisland25_resetsliders);
+ COMMAND(xgisland25_slidermd);
+ COMMAND(xgisland25_slidermw);
+ COMMAND(xgscpbtn);
+ COMMAND(xgisland1490_domecheck);
+ COMMAND(xgplateau3160_dopools);
+ COMMAND(xgwt200_scribetime);
+ COMMAND(xgwt900_scribe);
+ COMMAND(xgplaywhark);
+ COMMAND(xgrviewer);
+ COMMAND(xgwharksnd);
+ COMMAND(xglview_prisonoff);
+ COMMAND(xglview_villageoff);
+ COMMAND(xglviewer);
+ COMMAND(xglview_prisonon);
+ COMMAND(xglview_villageon);
+
+ // jspit (Jungle Island) external commands
+ COMMAND(xreseticons);
+ COMMAND(xicon);
+ COMMAND(xcheckicons);
+ COMMAND(xtoggleicon);
+ COMMAND(xjtunnel103_pictfix);
+ COMMAND(xjtunnel104_pictfix);
+ COMMAND(xjtunnel105_pictfix);
+ COMMAND(xjtunnel106_pictfix);
+ COMMAND(xvga1300_carriage);
+ COMMAND(xjdome25_resetsliders);
+ COMMAND(xjdome25_slidermd);
+ COMMAND(xjdome25_slidermw);
+ COMMAND(xjscpbtn);
+ COMMAND(xjisland3500_domecheck);
+ COMMAND(xhandlecontroldown);
+ COMMAND(xhandlecontrolmid);
+ COMMAND(xhandlecontrolup);
+ COMMAND(xjplaybeetle_550);
+ COMMAND(xjplaybeetle_600);
+ COMMAND(xjplaybeetle_950);
+ COMMAND(xjplaybeetle_1050);
+ COMMAND(xjplaybeetle_1450);
+ COMMAND(xjlagoon700_alert);
+ COMMAND(xjlagoon800_alert);
+ COMMAND(xjlagoon1500_alert);
+ COMMAND(xschool280_playwhark);
+ COMMAND(xjatboundary);
+
+ // ospit (Gehn's Office) external commands
+ COMMAND(xorollcredittime);
+ COMMAND(xbookclick);
+ COMMAND(xooffice30_closebook);
+ COMMAND(xobedroom5_closedrawer);
+ COMMAND(xogehnopenbook);
+ COMMAND(xogehnbookprevpage);
+ COMMAND(xogehnbooknextpage);
+ COMMAND(xgwatch);
+
+ // pspit (Prison Island) external commands
+ COMMAND(xpisland990_elevcombo);
+ COMMAND(xpscpbtn);
+ COMMAND(xpisland290_domecheck);
+ COMMAND(xpisland25_opencard);
+ COMMAND(xpisland25_resetsliders);
+ COMMAND(xpisland25_slidermd);
+ COMMAND(xpisland25_slidermw);
+
+ // rspit (Rebel Age) external commands
+ COMMAND(xrshowinventory);
+ COMMAND(xrhideinventory);
+ COMMAND(xrcredittime);
+ COMMAND(xrwindowsetup);
+
+ // tspit (Temple Island) external commands
+ COMMAND(xtexterior300_telescopedown);
+ COMMAND(xtexterior300_telescopeup);
+ COMMAND(xtisland390_covercombo);
+ COMMAND(xtatrusgivesbooks);
+ COMMAND(xtchotakesbook);
+ COMMAND(xthideinventory);
+ COMMAND(xt7500_checkmarbles);
+ COMMAND(xt7600_setupmarbles);
+ COMMAND(xt7800_setup);
+ COMMAND(xdrawmarbles);
+ COMMAND(xtakeit);
+ COMMAND(xtscpbtn);
+ COMMAND(xtisland4990_domecheck);
+ COMMAND(xtisland5056_opencard);
+ COMMAND(xtisland5056_resetsliders);
+ COMMAND(xtisland5056_slidermd);
+ COMMAND(xtisland5056_slidermw);
+ COMMAND(xtatboundary);
+
+ // Common external commands
+ COMMAND(xflies);
+}
+
+void RivenExternal::runCommand(uint16 argc, uint16 *argv) {
+ Common::String externalCommandName = _vm->getName(ExternalCommandNames, argv[0]);
+
+ for (uint16 i = 0; i < _externalCommands.size(); i++)
+ if (externalCommandName == _externalCommands[i]->desc) {
+ debug(0, "Running Riven External Command \'%s\'", externalCommandName.c_str());
+ (this->*(_externalCommands[i]->proc)) (argv[1], argv[1] ? argv + 2 : NULL);
+ return;
+ }
+
+ error("Unknown external command \'%s\'", externalCommandName.c_str());
+}
+
+void RivenExternal::runDemoBoundaryDialog() {
+ GUI::MessageDialog dialog("This demo does not allow you\n"
+ "to visit that part of Riven.");
+ dialog.runModal();
+}
+
+void RivenExternal::runEndGame(uint16 video) {
+ _vm->_sound->stopAllSLST();
+ _vm->_video->playMovieBlocking(video);
+
+ // TODO: Play until the last frame and then run the credits
+ _vm->_gameOver = true;
+}
+
+// ------------------------------------------------------------------------------------
+// aspit (Main Menu, Books, Setup) external commands
+// ------------------------------------------------------------------------------------
+
+void RivenExternal::xastartupbtnhide(uint16 argc, uint16 *argv) {
+ // The original game hides the start/setup buttons depending on an ini entry. It's safe to ignore this command.
+}
+
+void RivenExternal::xasetupcomplete(uint16 argc, uint16 *argv) {
+ // The original game sets an ini entry to disable the setup button and use the start button only. It's safe to ignore this part of the command.
+ _vm->_sound->stopSound();
+ _vm->changeToCard(1);
+}
+
+void RivenExternal::xaatrusopenbook(uint16 argc, uint16 *argv) {
+ // Get the variable
+ uint32 page = *_vm->matchVarToString("aatruspage");
+
+ // Set hotspots depending on the page
+ if (page == 1) {
+ _vm->_hotspots[1].enabled = false;
+ _vm->_hotspots[2].enabled = false;
+ _vm->_hotspots[3].enabled = true;
+ } else {
+ _vm->_hotspots[1].enabled = true;
+ _vm->_hotspots[2].enabled = true;
+ _vm->_hotspots[3].enabled = false;
+ }
+
+ // Draw the image of the page
+ _vm->_gfx->drawPLST(page);
+}
+
+void RivenExternal::xaatrusbookback(uint16 argc, uint16 *argv) {
+ // Return to where we were before entering the book
+ _vm->changeToStack(*_vm->matchVarToString("returnstackid"));
+ _vm->changeToCard(*_vm->matchVarToString("returncardid"));
+}
+
+void RivenExternal::xaatrusbookprevpage(uint16 argc, uint16 *argv) {
+ // Get the page variable
+ uint32 *page = _vm->matchVarToString("aatruspage");
+
+ // Decrement the page if it's not the first page
+ if (*page == 1)
+ return;
+ (*page)--;
+
+ // TODO: Play the page turning sound
+
+ // Now update the screen :)
+ _vm->_gfx->updateScreen();
+}
+
+void RivenExternal::xaatrusbooknextpage(uint16 argc, uint16 *argv) {
+ // Get the page variable
+ uint32 *page = _vm->matchVarToString("aatruspage");
+
+ // Increment the page if it's not the last page
+ if (((_vm->getFeatures() & GF_DEMO) && *page == 6) || *page == 10)
+ return;
+ (*page)++;
+
+ // TODO: Play the page turning sound
+
+ // Now update the screen :)
+ _vm->_gfx->updateScreen();
+}
+
+void RivenExternal::xacathopenbook(uint16 argc, uint16 *argv) {
+ // Get the variable
+ uint32 page = *_vm->matchVarToString("acathpage");
+
+ // Set hotspots depending on the page
+ if (page == 1) {
+ _vm->_hotspots[1].enabled = false;
+ _vm->_hotspots[2].enabled = false;
+ _vm->_hotspots[3].enabled = true;
+ } else {
+ _vm->_hotspots[1].enabled = true;
+ _vm->_hotspots[2].enabled = true;
+ _vm->_hotspots[3].enabled = false;
+ }
+
+ // Draw the image of the page
+ _vm->_gfx->drawPLST(page);
+
+ // Draw the white page edges
+ if (page > 1 && page < 5)
+ _vm->_gfx->drawPLST(50);
+ else if (page > 5)
+ _vm->_gfx->drawPLST(51);
+
+ if (page == 28) {
+ // TODO: Draw telescope combination
+ }
+}
+
+void RivenExternal::xacathbookback(uint16 argc, uint16 *argv) {
+ // Return to where we were before entering the book
+ _vm->changeToStack(*_vm->matchVarToString("returnstackid"));
+ _vm->changeToCard(*_vm->matchVarToString("returncardid"));
+}
+
+void RivenExternal::xacathbookprevpage(uint16 argc, uint16 *argv) {
+ // Get the variable
+ uint32 *page = _vm->matchVarToString("acathpage");
+
+ // Increment the page if it's not the first page
+ if (*page == 1)
+ return;
+ (*page)--;
+
+ // TODO: Play the page turning sound
+
+ // Now update the screen :)
+ _vm->_gfx->updateScreen();
+}
+
+void RivenExternal::xacathbooknextpage(uint16 argc, uint16 *argv) {
+ // Get the variable
+ uint32 *page = _vm->matchVarToString("acathpage");
+
+ // Increment the page if it's not the last page
+ if (*page == 49)
+ return;
+ (*page)++;
+
+ // TODO: Play the page turning sound
+
+ // Now update the screen :)
+ _vm->_gfx->updateScreen();
+}
+
+void RivenExternal::xtrapbookback(uint16 argc, uint16 *argv) {
+ // Return to where we were before entering the book
+ _vm->changeToStack(*_vm->matchVarToString("returnstackid"));
+ _vm->changeToCard(*_vm->matchVarToString("returncardid"));
+}
+
+void RivenExternal::xatrapbookclose(uint16 argc, uint16 *argv) {
+ // Close the trap book
+ _vm->_hotspots[1].enabled = false;
+ _vm->_hotspots[2].enabled = false;
+ _vm->_hotspots[3].enabled = false;
+ _vm->_hotspots[4].enabled = true;
+ _vm->_gfx->drawPLST(3);
+ _vm->_gfx->updateScreen();
+}
+
+void RivenExternal::xatrapbookopen(uint16 argc, uint16 *argv) {
+ // Open the trap book
+ _vm->_hotspots[1].enabled = true;
+ _vm->_hotspots[2].enabled = true;
+ _vm->_hotspots[3].enabled = true;
+ _vm->_hotspots[4].enabled = false;
+ _vm->_gfx->drawPLST(1);
+ // TODO: Play movie
+ _vm->_gfx->updateScreen();
+}
+
+void RivenExternal::xarestoregame(uint16 argc, uint16 *argv) {
+ // Launch the load game dialog
+ _vm->runLoadDialog();
+}
+
+void RivenExternal::xadisablemenureturn(uint16 argc, uint16 *argv) {
+ // Dummy function -- implemented in Mohawk::go
+}
+
+void RivenExternal::xaenablemenureturn(uint16 argc, uint16 *argv) {
+ // Dummy function -- implemented in Mohawk::go
+}
+
+void RivenExternal::xalaunchbrowser(uint16 argc, uint16 *argv) {
+ // Well, we can't launch a browser for obvious reasons ;)
+ GUI::MessageDialog dialog("At this point, the Riven Demo would\n"
+ "open up a web browser to bring you to\n"
+ "the Riven website. ScummVM cannot do\n"
+ "that. Visit the site on your own.");
+ dialog.runModal();
+}
+
+// ------------------------------------------------------------------------------------
+// bspit (Bookmaking Island) external commands
+// ------------------------------------------------------------------------------------
+
+void RivenExternal::xblabopenbook(uint16 argc, uint16 *argv) {
+ // Get the variable
+ uint32 page = *_vm->matchVarToString("blabbook");
+
+ // Draw the image of the page based on the blabbook variable
+ _vm->_gfx->drawPLST(page);
+
+ // TODO: Draw the dome combo
+ if (page == 14) {
+ warning ("Need to draw dome combo");
+ }
+}
+
+void RivenExternal::xblabbookprevpage(uint16 argc, uint16 *argv) {
+ // Get the page variable
+ uint32 *page = _vm->matchVarToString("blabbook");
+
+ // Decrement the page if it's not the first page
+ if (*page == 1)
+ return;
+ (*page)--;
+
+ // Now update the screen :)
+ _vm->_gfx->updateScreen();
+}
+
+void RivenExternal::xblabbooknextpage(uint16 argc, uint16 *argv) {
+ // Get the page variable
+ uint32 *page = _vm->matchVarToString("blabbook");
+
+ // Increment the page if it's not the last page
+ if (*page == 22)
+ return;
+ (*page)++;
+
+ // Now update the screen :)
+ _vm->_gfx->updateScreen();
+}
+
+void RivenExternal::xsoundplug(uint16 argc, uint16 *argv) {
+ uint32 heat = *_vm->matchVarToString("bheat");
+ uint32 boilerInactive = *_vm->matchVarToString("bcratergg");
+
+ if (heat != 0)
+ _vm->_sound->playSLST(1, _vm->getCurCard());
+ else if (boilerInactive != 0)
+ _vm->_sound->playSLST(2, _vm->getCurCard());
+ else
+ _vm->_sound->playSLST(3, _vm->getCurCard());
+}
+
+void RivenExternal::xbchangeboiler(uint16 argc, uint16 *argv) {
+ uint32 heat = *_vm->matchVarToString("bheat");
+ uint32 water = *_vm->matchVarToString("bblrwtr");
+ uint32 platform = *_vm->matchVarToString("bblrgrt");
+
+ if (argv[0] == 1) {
+ if (water == 0) {
+ if (platform == 0)
+ _vm->_video->activateMLST(10, _vm->getCurCard());
+ else
+ _vm->_video->activateMLST(12, _vm->getCurCard());
+ } else if (heat == 0) {
+ if (platform == 0)
+ _vm->_video->activateMLST(19, _vm->getCurCard());
+ else
+ _vm->_video->activateMLST(22, _vm->getCurCard());
+ } else {
+ if (platform == 0)
+ _vm->_video->activateMLST(13, _vm->getCurCard());
+ else
+ _vm->_video->activateMLST(16, _vm->getCurCard());
+ }
+ } else if (argv[0] == 2 && water != 0) {
+ if (heat == 0) {
+ if (platform == 0)
+ _vm->_video->activateMLST(20, _vm->getCurCard());
+ else
+ _vm->_video->activateMLST(23, _vm->getCurCard());
+ } else {
+ if (platform == 0)
+ _vm->_video->activateMLST(18, _vm->getCurCard());
+ else
+ _vm->_video->activateMLST(15, _vm->getCurCard());
+ }
+ } else if (argv[0] == 3) {
+ if (platform == 0) {
+ if (water == 0) {
+ _vm->_video->activateMLST(11, _vm->getCurCard());
+ } else {
+ if (heat == 0)
+ _vm->_video->activateMLST(17, _vm->getCurCard());
+ else
+ _vm->_video->activateMLST(24, _vm->getCurCard());
+ }
+ } else {
+ if (water == 0) {
+ _vm->_video->activateMLST(9, _vm->getCurCard());
+ } else {
+ if (heat == 0)
+ _vm->_video->activateMLST(14, _vm->getCurCard());
+ else
+ _vm->_video->activateMLST(21, _vm->getCurCard());
+ }
+ }
+ }
+
+ if (argc > 1)
+ _vm->_sound->playSLST(argv[1], _vm->getCurCard());
+ else if (argv[0] == 2)
+ _vm->_sound->playSLST(1, _vm->getCurCard());
+
+ _vm->_video->playMovie(11);
+}
+
+void RivenExternal::xbupdateboiler(uint16 argc, uint16 *argv) {
+ uint32 heat = *_vm->matchVarToString("bheat");
+ uint32 platform = *_vm->matchVarToString("bblrgrt");
+
+ if (heat) {
+ if (platform == 0) {
+ _vm->_video->activateMLST(7, _vm->getCurCard());
+ // TODO: Play video (non-blocking)
+ } else {
+ _vm->_video->activateMLST(8, _vm->getCurCard());
+ // TODO: Play video (non-blocking)
+ }
+ } else {
+ // TODO: Stop MLST's 7 and 8
+ }
+
+ _vm->changeToCard();
+}
+
+void RivenExternal::xbsettrap(uint16 argc, uint16 *argv) {
+ // TODO: Set the Ytram trap
+}
+
+void RivenExternal::xbcheckcatch(uint16 argc, uint16 *argv) {
+ // TODO: Check if we've caught a Ytram
+}
+
+void RivenExternal::xbait(uint16 argc, uint16 *argv) {
+ // Set the cursor to the pellet
+ _vm->_gfx->changeCursor(kRivenPelletCursor);
+
+ // Loop until the player lets go (or quits)
+ Common::Event event;
+ bool mouseDown = true;
+ while (mouseDown) {
+ while (_vm->_system->getEventManager()->pollEvent(event)) {
+ if (event.type == Common::EVENT_LBUTTONUP)
+ mouseDown = false;
+ else if (event.type == Common::EVENT_MOUSEMOVE)
+ _vm->_system->updateScreen();
+ else if (event.type == Common::EVENT_QUIT || event.type == Common::EVENT_RTL)
+ return;
+ }
+
+ _vm->_system->delayMillis(10); // Take it easy on the CPU
+ }
+
+ // Set back the cursor
+ _vm->_gfx->changeCursor(kRivenMainCursor);
+
+ // Set the bait if we put it on the plate
+ if (_vm->_hotspots[9].rect.contains(_vm->_system->getEventManager()->getMousePos())) {
+ *_vm->matchVarToString("bbait") = 1;
+ _vm->_gfx->drawPLST(4);
+ _vm->_gfx->updateScreen();
+ _vm->_hotspots[3].enabled = false; // Disable bait hotspot
+ _vm->_hotspots[9].enabled = true; // Enable baitplate hotspot
+ }
+}
+
+void RivenExternal::xbfreeytram(uint16 argc, uint16 *argv) {
+ // TODO: Play a random Ytram movie
+}
+
+void RivenExternal::xbaitplate(uint16 argc, uint16 *argv) {
+ // Remove the pellet from the plate and put it in your hand
+ _vm->_gfx->drawPLST(3);
+ _vm->_gfx->updateScreen();
+ _vm->_gfx->changeCursor(kRivenPelletCursor);
+
+ // Loop until the player lets go (or quits)
+ Common::Event event;
+ bool mouseDown = true;
+ while (mouseDown) {
+ while (_vm->_system->getEventManager()->pollEvent(event)) {
+ if (event.type == Common::EVENT_LBUTTONUP)
+ mouseDown = false;
+ else if (event.type == Common::EVENT_MOUSEMOVE)
+ _vm->_system->updateScreen();
+ else if (event.type == Common::EVENT_QUIT || event.type == Common::EVENT_RTL)
+ return;
+ }
+
+ _vm->_system->delayMillis(10); // Take it easy on the CPU
+ }
+
+ // Set back the cursor
+ _vm->_gfx->changeCursor(kRivenMainCursor);
+
+ // Set the bait if we put it on the plate, remove otherwise
+ if (_vm->_hotspots[9].rect.contains(_vm->_system->getEventManager()->getMousePos())) {
+ *_vm->matchVarToString("bbait") = 1;
+ _vm->_gfx->drawPLST(4);
+ _vm->_gfx->updateScreen();
+ _vm->_hotspots[3].enabled = false; // Disable bait hotspot
+ _vm->_hotspots[9].enabled = true; // Enable baitplate hotspot
+ } else {
+ *_vm->matchVarToString("bbait") = 0;
+ _vm->_hotspots[3].enabled = true; // Enable bait hotspot
+ _vm->_hotspots[9].enabled = false; // Disable baitplate hotspot
+ }
+}
+
+void RivenExternal::xbisland190_opencard(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xbisland190_resetsliders(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xbisland190_slidermd(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xbisland190_slidermw(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xbscpbtn(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xbisland_domecheck(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xvalvecontrol(uint16 argc, uint16 *argv) {
+ // Get the variable for the valve
+ uint32 *valve = _vm->matchVarToString("bvalve");
+
+ Common::Event event;
+ int changeX = 0;
+ int changeY = 0;
+
+ // Set the cursor to the closed position
+ _vm->_gfx->changeCursor(2004);
+ _vm->_system->updateScreen();
+
+ for (;;) {
+ while (_vm->_system->getEventManager()->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_MOUSEMOVE:
+ changeX = event.mouse.x - _vm->_mousePos.x;
+ changeY = _vm->_mousePos.y - event.mouse.y;
+ _vm->_system->updateScreen();
+ break;
+ case Common::EVENT_LBUTTONUP:
+ // FIXME: These values for changes in x/y could be tweaked.
+ if (*valve == 0 && changeY <= -10) {
+ *valve = 1;
+ // TODO: Play movie
+ _vm->changeToCard(); // Refresh
+ } else if (*valve == 1) {
+ if (changeX >= 0 && changeY >= 10) {
+ *valve = 0;
+ // TODO: Play movie
+ _vm->changeToCard(); // Refresh
+ } else if (changeX <= -10 && changeY <= 10) {
+ *valve = 2;
+ // TODO: Play movie
+ _vm->changeToCard(); // Refresh
+ }
+ } else if (*valve == 2 && changeX >= 10) {
+ *valve = 1;
+ // TODO: Play movie
+ _vm->changeToCard(); // Refresh
+ }
+ return;
+ default:
+ break;
+ }
+ }
+ _vm->_system->delayMillis(10);
+ }
+}
+
+void RivenExternal::xbchipper(uint16 argc, uint16 *argv) {
+ // Why is this an external command....?
+ if (*_vm->matchVarToString("bvalve") == 2)
+ _vm->_video->playMovieBlocking(2);
+}
+
+// ------------------------------------------------------------------------------------
+// gspit (Garden Island) external commands
+// ------------------------------------------------------------------------------------
+
+void RivenExternal::xgresetpins(uint16 argc, uint16 *argv) {
+ // TODO: Map related
+}
+
+void RivenExternal::xgrotatepins(uint16 argc, uint16 *argv) {
+ // TODO: Map related
+}
+
+void RivenExternal::xgpincontrols(uint16 argc, uint16 *argv) {
+ // TODO: Map related
+}
+
+void RivenExternal::xgisland25_opencard(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xgisland25_resetsliders(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xgisland25_slidermd(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xgisland25_slidermw(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xgscpbtn(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xgisland1490_domecheck(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xgplateau3160_dopools(uint16 argc, uint16 *argv) {
+ // TODO: "Bubble" map related
+}
+
+void RivenExternal::xgwt200_scribetime(uint16 argc, uint16 *argv) {
+ // Get the current time
+ *_vm->matchVarToString("gscribetime") = _vm->_system->getMillis();
+}
+
+void RivenExternal::xgwt900_scribe(uint16 argc, uint16 *argv) {
+ uint32 *scribeVar = _vm->matchVarToString("gscribe");
+
+ if (*scribeVar == 1 && _vm->_system->getMillis() > *_vm->matchVarToString("gscribetime") + 40000)
+ *scribeVar = 2;
+}
+
+void RivenExternal::xgplaywhark(uint16 argc, uint16 *argv) {
+ // TODO: Whark response to using the lights
+}
+
+void RivenExternal::xgrviewer(uint16 argc, uint16 *argv) {
+ // TODO: Image viewer related
+}
+
+void RivenExternal::xgwharksnd(uint16 argc, uint16 *argv) {
+ // TODO: Image viewer related
+}
+
+void RivenExternal::xglview_prisonoff(uint16 argc, uint16 *argv) {
+ // TODO: Image viewer related
+}
+
+void RivenExternal::xglview_villageoff(uint16 argc, uint16 *argv) {
+ // TODO: Image viewer related
+}
+
+void RivenExternal::xglviewer(uint16 argc, uint16 *argv) {
+ // TODO: Image viewer related
+}
+
+void RivenExternal::xglview_prisonon(uint16 argc, uint16 *argv) {
+ // TODO: Image viewer related
+}
+
+void RivenExternal::xglview_villageon(uint16 argc, uint16 *argv) {
+ // TODO: Image viewer related
+}
+
+// ------------------------------------------------------------------------------------
+// jspit (Jungle Island) external commands
+// ------------------------------------------------------------------------------------
+
+void RivenExternal::xreseticons(uint16 argc, uint16 *argv) {
+ // Reset the icons when going to Tay (rspit)
+ *_vm->matchVarToString("jicons") = 0;
+ *_vm->matchVarToString("jiconorder") = 0;
+ *_vm->matchVarToString("jrbook") = 0;
+}
+
+// Count up how many icons are pressed
+static byte countDepressedIcons(uint32 iconOrderVar) {
+ if (iconOrderVar >= (1 << 20))
+ return 5;
+ else if (iconOrderVar >= (1 << 15))
+ return 4;
+ else if (iconOrderVar >= (1 << 10))
+ return 3;
+ else if (iconOrderVar >= (1 << 5))
+ return 2;
+ else if (iconOrderVar >= (1 << 1))
+ return 1;
+ else
+ return 0;
+}
+
+void RivenExternal::xicon(uint16 argc, uint16 *argv) {
+ // Set atemp as the status of whether or not the icon can be depressed.
+ if (*_vm->matchVarToString("jicons") & (1 << (argv[0] - 1))) {
+ // This icon is depressed. Allow depression only if the last depressed icon was this one.
+ if ((*_vm->matchVarToString("jiconorder") & 0x1f) == argv[0])
+ *_vm->matchVarToString("atemp") = 1;
+ else
+ *_vm->matchVarToString("atemp") = 2;
+ } else
+ *_vm->matchVarToString("atemp") = 0;
+}
+
+void RivenExternal::xcheckicons(uint16 argc, uint16 *argv) {
+ // Reset the icons if this is the sixth icon
+ uint32 *iconOrderVar = _vm->matchVarToString("jiconorder");
+ if (countDepressedIcons(*iconOrderVar) == 5) {
+ *iconOrderVar = 0;
+ *_vm->matchVarToString("jicons") = 0;
+ _vm->_sound->playSound(46, false);
+ }
+}
+
+void RivenExternal::xtoggleicon(uint16 argc, uint16 *argv) {
+ // Get the variables
+ uint32 *iconsDepressed = _vm->matchVarToString("jicons");
+ uint32 *iconOrderVar = _vm->matchVarToString("jiconorder");
+
+ if (*iconsDepressed & (1 << (argv[0] - 1))) {
+ // The icon is depressed, now unpress it
+ *iconsDepressed &= ~(1 << (argv[0] - 1));
+ *iconOrderVar >>= 5;
+ } else {
+ // The icon is not depressed, now depress it
+ *iconsDepressed |= 1 << (argv[0] - 1);
+ *iconOrderVar = (*iconOrderVar << 5) + argv[0];
+ }
+
+ // Check if the puzzle is complete now and assign 1 to jrbook if the puzzle is complete.
+ if (*iconOrderVar == *_vm->matchVarToString("jiconcorrectorder"))
+ *_vm->matchVarToString("jrbook") = 1;
+}
+
+void RivenExternal::xjtunnel103_pictfix(uint16 argc, uint16 *argv) {
+ // Get the jicons variable which contains which of the stones are depressed in the rebel tunnel puzzle
+ uint32 iconsDepressed = *_vm->matchVarToString("jicons");
+
+ // Now, draw which icons are depressed based on the bits of the variable
+ if (iconsDepressed & (1 << 0))
+ _vm->_gfx->drawPLST(2);
+ if (iconsDepressed & (1 << 1))
+ _vm->_gfx->drawPLST(3);
+ if (iconsDepressed & (1 << 2))
+ _vm->_gfx->drawPLST(4);
+ if (iconsDepressed & (1 << 3))
+ _vm->_gfx->drawPLST(5);
+ if (iconsDepressed & (1 << 22))
+ _vm->_gfx->drawPLST(6);
+ if (iconsDepressed & (1 << 23))
+ _vm->_gfx->drawPLST(7);
+ if (iconsDepressed & (1 << 24))
+ _vm->_gfx->drawPLST(8);
+}
+
+void RivenExternal::xjtunnel104_pictfix(uint16 argc, uint16 *argv) {
+ // Get the jicons variable which contains which of the stones are depressed in the rebel tunnel puzzle
+ uint32 iconsDepressed = *_vm->matchVarToString("jicons");
+
+ // Now, draw which icons are depressed based on the bits of the variable
+ if (iconsDepressed & (1 << 9))
+ _vm->_gfx->drawPLST(2);
+ if (iconsDepressed & (1 << 10))
+ _vm->_gfx->drawPLST(3);
+ if (iconsDepressed & (1 << 11))
+ _vm->_gfx->drawPLST(4);
+ if (iconsDepressed & (1 << 12))
+ _vm->_gfx->drawPLST(5);
+ if (iconsDepressed & (1 << 13))
+ _vm->_gfx->drawPLST(6);
+ if (iconsDepressed & (1 << 14))
+ _vm->_gfx->drawPLST(7);
+ if (iconsDepressed & (1 << 15))
+ _vm->_gfx->drawPLST(8);
+ if (iconsDepressed & (1 << 16))
+ _vm->_gfx->drawPLST(9);
+}
+
+void RivenExternal::xjtunnel105_pictfix(uint16 argc, uint16 *argv) {
+ // Get the jicons variable which contains which of the stones are depressed in the rebel tunnel puzzle
+ uint32 iconsDepressed = *_vm->matchVarToString("jicons");
+
+ // Now, draw which icons are depressed based on the bits of the variable
+ if (iconsDepressed & (1 << 3))
+ _vm->_gfx->drawPLST(2);
+ if (iconsDepressed & (1 << 4))
+ _vm->_gfx->drawPLST(3);
+ if (iconsDepressed & (1 << 5))
+ _vm->_gfx->drawPLST(4);
+ if (iconsDepressed & (1 << 6))
+ _vm->_gfx->drawPLST(5);
+ if (iconsDepressed & (1 << 7))
+ _vm->_gfx->drawPLST(6);
+ if (iconsDepressed & (1 << 8))
+ _vm->_gfx->drawPLST(7);
+ if (iconsDepressed & (1 << 9))
+ _vm->_gfx->drawPLST(8);
+}
+
+void RivenExternal::xjtunnel106_pictfix(uint16 argc, uint16 *argv) {
+ // Get the jicons variable which contains which of the stones are depressed in the rebel tunnel puzzle
+ uint32 iconsDepressed = *_vm->matchVarToString("jicons");
+
+ // Now, draw which icons are depressed based on the bits of the variable
+ if (iconsDepressed & (1 << 16))
+ _vm->_gfx->drawPLST(2);
+ if (iconsDepressed & (1 << 17))
+ _vm->_gfx->drawPLST(3);
+ if (iconsDepressed & (1 << 18))
+ _vm->_gfx->drawPLST(4);
+ if (iconsDepressed & (1 << 19))
+ _vm->_gfx->drawPLST(5);
+ if (iconsDepressed & (1 << 20))
+ _vm->_gfx->drawPLST(6);
+ if (iconsDepressed & (1 << 21))
+ _vm->_gfx->drawPLST(7);
+ if (iconsDepressed & (1 << 22))
+ _vm->_gfx->drawPLST(8);
+ if (iconsDepressed & (1 << 23))
+ _vm->_gfx->drawPLST(9);
+}
+
+void RivenExternal::xvga1300_carriage(uint16 argc, uint16 *argv) {
+ // TODO: This function is supposed to do a lot more, something like this (pseudocode):
+
+ // Show level pull movie
+ // Set transition up
+ // Change to up card
+ // Show movie of carriage coming down
+ // Set transition down
+ // Change back to card 276
+ // Show movie of carriage coming down
+ // if jgallows == 0
+ // Set up timer
+ // Enter new input loop
+ // if you click within the time
+ // move forward
+ // set transition right
+ // change to card right
+ // show movie of ascending
+ // change to card 263
+ // else
+ // show movie of carriage ascending only
+ // else
+ // show movie of carriage ascending only
+
+
+ // For now, if the gallows base is closed, assume ascension and move to that card.
+ if (*_vm->matchVarToString("jgallows") == 0)
+ _vm->changeToCard(_vm->matchRMAPToCard(0x17167));
+}
+
+void RivenExternal::xjdome25_resetsliders(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xjdome25_slidermd(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xjdome25_slidermw(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xjscpbtn(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xjisland3500_domecheck(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+int RivenExternal::jspitElevatorLoop() {
+ Common::Event event;
+ int changeLevel = 0;
+
+ _vm->_gfx->changeCursor(2004);
+ _vm->_system->updateScreen();
+ for (;;) {
+ while (_vm->_system->getEventManager()->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_MOUSEMOVE:
+ if (event.mouse.y > (_vm->_mousePos.y + 10)) {
+ changeLevel = -1;
+ } else if (event.mouse.y < (_vm->_mousePos.y - 10)) {
+ changeLevel = 1;
+ } else {
+ changeLevel = 0;
+ }
+ _vm->_system->updateScreen();
+ break;
+ case Common::EVENT_LBUTTONUP:
+ _vm->_gfx->changeCursor(kRivenMainCursor);
+ _vm->_system->updateScreen();
+ return changeLevel;
+ default:
+ break;
+ }
+ }
+ _vm->_system->delayMillis(10);
+ }
+}
+
+void RivenExternal::xhandlecontrolup(uint16 argc, uint16 *argv) {
+ int changeLevel = jspitElevatorLoop();
+
+ if (changeLevel == -1) {
+ // TODO: Run movie
+ _vm->changeToCard(_vm->matchRMAPToCard(0x1e374));
+ }
+}
+
+void RivenExternal::xhandlecontroldown(uint16 argc, uint16 *argv) {
+ int changeLevel = jspitElevatorLoop();
+
+ if (changeLevel == 1) {
+ // TODO: Run movie
+ _vm->changeToCard(_vm->matchRMAPToCard(0x1e374));
+ }
+}
+
+void RivenExternal::xhandlecontrolmid(uint16 argc, uint16 *argv) {
+ int changeLevel = jspitElevatorLoop();
+
+ if (changeLevel == 1) {
+ // TODO: Run movie
+ _vm->changeToCard(_vm->matchRMAPToCard(0x1e597));
+ } else if (changeLevel == -1) {
+ // TODO: Run movie
+ _vm->changeToCard(_vm->matchRMAPToCard(0x1e29c));
+ }
+}
+
+void RivenExternal::xjplaybeetle_550(uint16 argc, uint16 *argv) {
+ // Play a beetle animation 25% of the time
+ *_vm->matchVarToString("jplaybeetle") = (_rnd->getRandomNumberRng(0, 3) == 0) ? 1 : 0;
+}
+
+void RivenExternal::xjplaybeetle_600(uint16 argc, uint16 *argv) {
+ // Play a beetle animation 25% of the time
+ *_vm->matchVarToString("jplaybeetle") = (_rnd->getRandomNumberRng(0, 3) == 0) ? 1 : 0;
+}
+
+void RivenExternal::xjplaybeetle_950(uint16 argc, uint16 *argv) {
+ // Play a beetle animation 25% of the time
+ *_vm->matchVarToString("jplaybeetle") = (_rnd->getRandomNumberRng(0, 3) == 0) ? 1 : 0;
+}
+
+void RivenExternal::xjplaybeetle_1050(uint16 argc, uint16 *argv) {
+ // Play a beetle animation 25% of the time
+ *_vm->matchVarToString("jplaybeetle") = (_rnd->getRandomNumberRng(0, 3) == 0) ? 1 : 0;
+}
+
+void RivenExternal::xjplaybeetle_1450(uint16 argc, uint16 *argv) {
+ // Play a beetle animation 25% of the time as long as the girl is not present
+ *_vm->matchVarToString("jplaybeetle") = (_rnd->getRandomNumberRng(0, 3) == 0 && *_vm->matchVarToString("jgirl") != 1) ? 1 : 0;
+}
+
+void RivenExternal::xjlagoon700_alert(uint16 argc, uint16 *argv) {
+ // TODO: Sunner related
+}
+
+void RivenExternal::xjlagoon800_alert(uint16 argc, uint16 *argv) {
+ // TODO: Sunner related
+}
+
+void RivenExternal::xjlagoon1500_alert(uint16 argc, uint16 *argv) {
+ // Have the sunners move a bit as you get closer ;)
+ uint32 *sunners = _vm->matchVarToString("jsunners");
+ if (*sunners == 0) {
+ _vm->_video->playMovieBlocking(3);
+ } else if (*sunners == 1) {
+ _vm->_video->playMovieBlocking(2);
+ *sunners = 2;
+ }
+}
+
+void RivenExternal::xschool280_playwhark(uint16 argc, uint16 *argv) {
+ // TODO: The "monstrous" whark puzzle that teaches the number system
+}
+
+void RivenExternal::xjatboundary(uint16 argc, uint16 *argv) {
+ runDemoBoundaryDialog();
+}
+
+// ------------------------------------------------------------------------------------
+// ospit (Gehn's Office) external commands
+// ------------------------------------------------------------------------------------
+
+void RivenExternal::xorollcredittime(uint16 argc, uint16 *argv) {
+ // WORKAROUND: The special change stuff only handles one destination and it would
+ // be messy to modify the way that currently works. If we use the trap book on Tay,
+ // we should be using the Tay end game sequences.
+ if (*_vm->matchVarToString("returnstackid") == rspit) {
+ _vm->changeToStack(rspit);
+ _vm->changeToCard(2);
+ return;
+ }
+
+ // You used the trap book... why? What were you thinking?
+ uint32 *gehnState = _vm->matchVarToString("agehn");
+
+ if (*gehnState == 0) // Gehn who?
+ runEndGame(1);
+ else if (*gehnState == 4) // You freed him? Are you kidding me?
+ runEndGame(2);
+ else // You already spoke with Gehn. What were you thinking?
+ runEndGame(3);
+}
+
+void RivenExternal::xbookclick(uint16 argc, uint16 *argv) {
+ // TODO: This fun external command is probably one of the most complex,
+ // up there with the marble puzzle ones. It involves so much... Basically,
+ // it's playing when Gehn holds the trap book up to you and you have to
+ // click on the book (hence the name of the function). Yeah, not fun.
+ // Lots of timing stuff needs to be done for a couple videos.
+}
+
+void RivenExternal::xooffice30_closebook(uint16 argc, uint16 *argv) {
+ // Close the blank linking book if it's open
+ uint32 *book = _vm->matchVarToString("odeskbook");
+ if (*book != 1)
+ return;
+
+ // Set the variable to be "closed"
+ *book = 0;
+
+ // Play the movie
+ _vm->_video->playMovieBlocking(1);
+
+ // Set the hotspots into their correct states
+ _vm->_hotspots[2].enabled = false;
+ _vm->_hotspots[5].enabled = false;
+ _vm->_hotspots[6].enabled = true;
+
+ // We now need to draw PLST 1 and refresh, but PLST 1 is
+ // drawn when refreshing anyway, so don't worry about that.
+ _vm->changeToCard();
+}
+
+void RivenExternal::xobedroom5_closedrawer(uint16 argc, uint16 *argv) {
+ // Close the drawer if open when clicking on the journal.
+ _vm->_video->playMovieBlocking(2);
+ *_vm->matchVarToString("ostanddrawer") = 0;
+}
+
+void RivenExternal::xogehnopenbook(uint16 argc, uint16 *argv) {
+ _vm->_gfx->drawPLST(*_vm->matchVarToString("ogehnpage"));
+}
+
+void RivenExternal::xogehnbookprevpage(uint16 argc, uint16 *argv) {
+ // Get the page variable
+ uint32 *page = _vm->matchVarToString("ogehnpage");
+
+ // Decrement the page if it's not the first page
+ if (*page == 1)
+ return;
+ (*page)--;
+
+ // TODO: Play the page turning sound
+
+ // Now update the screen :)
+ _vm->_gfx->updateScreen();
+}
+
+void RivenExternal::xogehnbooknextpage(uint16 argc, uint16 *argv) {
+ // Get the page variable
+ uint32 *page = _vm->matchVarToString("ogehnpage");
+
+ // Increment the page if it's not the last page
+ if (*page == 13)
+ return;
+ (*page)++;
+
+ // TODO: Play the page turning sound
+
+ // Now update the screen :)
+ _vm->_gfx->updateScreen();
+}
+
+void RivenExternal::xgwatch(uint16 argc, uint16 *argv) {
+ // TODO: Plays the prison combo on the watch
+}
+
+// ------------------------------------------------------------------------------------
+// pspit (Prison Island) external commands
+// ------------------------------------------------------------------------------------
+
+// Yeah, none of these are done yet :P
+
+void RivenExternal::xpisland990_elevcombo(uint16 argc, uint16 *argv) {
+ // TODO: Elevator combo check
+}
+
+void RivenExternal::xpscpbtn(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xpisland290_domecheck(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xpisland25_opencard(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xpisland25_resetsliders(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xpisland25_slidermd(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xpisland25_slidermw(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+// ------------------------------------------------------------------------------------
+// rspit (Rebel Age) external commands
+// ------------------------------------------------------------------------------------
+
+void RivenExternal::xrcredittime(uint16 argc, uint16 *argv) {
+ // Nice going, you used the trap book on Tay.
+
+ // The game chooses what ending based on agehn for us,
+ // so we just have to play the video and credits.
+ // For the record, when agehn == 4, Gehn will thank you for
+ // showing him the rebel age and then leave you to die.
+ // Otherwise, the rebels burn the book. Epic fail either way.
+ runEndGame(1);
+}
+
+void RivenExternal::xrshowinventory(uint16 argc, uint16 *argv) {
+ // Give the trap book and Catherine's journal to the player
+ *_vm->matchVarToString("atrapbook") = 1;
+ *_vm->matchVarToString("acathbook") = 1;
+ _vm->_gfx->showInventory();
+}
+
+void RivenExternal::xrhideinventory(uint16 argc, uint16 *argv) {
+ _vm->_gfx->hideInventory();
+}
+
+void RivenExternal::xrwindowsetup(uint16 argc, uint16 *argv) {
+ // TODO: Randomizing what effect happens when you look out into the middle of Tay (useless! :P)
+}
+
+// ------------------------------------------------------------------------------------
+// tspit (Temple Island) external commands
+// ------------------------------------------------------------------------------------
+
+void RivenExternal::xtexterior300_telescopedown(uint16 argc, uint16 *argv) {
+ // First, show the button movie
+ _vm->_video->playMovieBlocking(3);
+
+ // Don't do anything else if the telescope power is off
+ if (*_vm->matchVarToString("ttelevalve") == 0)
+ return;
+
+ uint32 *telescopePos = _vm->matchVarToString("ttelescope");
+ uint32 *telescopeCover = _vm->matchVarToString("ttelecover");
+
+ if (*telescopePos == 1) {
+ // We're at the bottom, which means one of two things can happen...
+ if (*telescopeCover == 1 && *_vm->matchVarToString("ttelepin") == 1) {
+ // ...if the cover is open and the pin is up, the game is now over.
+ if (*_vm->matchVarToString("pcage") == 2) {
+ // The best ending: Catherine is free, Gehn is trapped, Atrus comes to rescue you.
+ // And now we fall back to Earth... all the way...
+ warning("xtexterior300_telescopedown: Good ending");
+ _vm->_video->activateMLST(8, _vm->getCurCard());
+ runEndGame(8);
+ } else if (*_vm->matchVarToString("agehn") == 4) {
+ // The ok ending: Catherine is still trapped, Gehn is trapped, Atrus comes to rescue you.
+ // Nice going! Catherine and the islanders are all dead now! Just go back to your home...
+ warning("xtexterior300_telescopedown: OK ending");
+ _vm->_video->activateMLST(9, _vm->getCurCard());
+ runEndGame(9);
+ } else if (*_vm->matchVarToString("atrapbook") == 1) {
+ // The bad ending: Catherine is trapped, Gehn is free, Atrus gets shot by Gehn,
+ // And then you get shot by Cho. Nice going! Catherine and the islanders are dead
+ // and you have just set Gehn free from Riven, not to mention you're dead.
+ warning("xtexterior300_telescopedown: Bad ending");
+ _vm->_video->activateMLST(10, _vm->getCurCard());
+ runEndGame(10);
+ } else {
+ // The impossible ending: You don't have Catherine's journal and yet you were somehow
+ // able to open the hatch on the telescope. The game provides an ending for those who
+ // cheat, load a saved game with the combo, or just guess the telescope combo. Atrus
+ // doesn't come and you just fall into the fissure.
+ warning("xtexterior300_telescopedown: Wtf ending");
+ _vm->_video->activateMLST(11, _vm->getCurCard());
+ runEndGame(11);
+ }
+ } else {
+ // ...the telescope can't move down anymore.
+ // TODO: Play sound
+ }
+ } else {
+ // We're not at the bottom, and we can move down again
+
+ // TODO: Down movie, it involves playing a chunk of a movie
+
+ // Now move the telescope down a position and refresh
+ *telescopePos -= 1;
+ _vm->changeToCard();
+ }
+}
+
+void RivenExternal::xtexterior300_telescopeup(uint16 argc, uint16 *argv) {
+ // First, show the button movie
+ _vm->_video->playMovieBlocking(3);
+
+ // Don't do anything else if the telescope power is off
+ if (*_vm->matchVarToString("ttelevalve") == 0)
+ return;
+
+ uint32 *telescopePos = _vm->matchVarToString("ttelescope");
+
+ // Check if we can't move up anymore
+ if (*telescopePos == 5) {
+ // TODO: Play sound
+ return;
+ }
+
+ // TODO: Up movie, it involves playing a chunk of a movie
+
+ // Now move the telescope up a position and refresh
+ *telescopePos += 1;
+ _vm->changeToCard();
+}
+
+void RivenExternal::xtisland390_covercombo(uint16 argc, uint16 *argv) {
+ // Called when clicking the telescope cover buttons. button is the button number (1...5).
+ uint32 *pressedButtons = _vm->matchVarToString("tcovercombo");
+
+ // We pressed a button! Yay! Add it to the queue.
+ *pressedButtons *= 10;
+ *pressedButtons += argv[0];
+
+ if (*pressedButtons == *_vm->matchVarToString("tcorrectorder")) {
+ _vm->_hotspots[9].enabled = true;
+ } else {
+ _vm->_hotspots[9].enabled = false;
+
+ // Set the buttons to the last one pressed if we've
+ // pressed more than 5 buttons.
+ if (*pressedButtons > 55555)
+ *pressedButtons = argv[0];
+ }
+}
+
+// Atrus' Journal and Trap Book are added to inventory
+void RivenExternal::xtatrusgivesbooks(uint16 argc, uint16 *argv) {
+ // Give the player Atrus' Journal and the Trap book
+ *_vm->matchVarToString("aatrusbook") = 1;
+ *_vm->matchVarToString("atrapbook") = 1;
+
+ // Randomize the telescope combination
+ uint32 *teleCombo = _vm->matchVarToString("tcorrectorder");
+ for (byte i = 0; i < 5; i++) {
+ *teleCombo *= 10;
+ *teleCombo += _rnd->getRandomNumberRng(1, 5);
+ }
+
+ // TODO: Randomize Dome Combination
+}
+
+// Trap Book is removed from inventory
+void RivenExternal::xtchotakesbook(uint16 argc, uint16 *argv) {
+ // And now Cho takes the trap book. Sure, this isn't strictly
+ // necessary to add and them remove the trap book... but it
+ // seems better to do this ;)
+ *_vm->matchVarToString("atrapbook") = 0;
+}
+
+void RivenExternal::xthideinventory(uint16 argc, uint16 *argv) {
+ _vm->_gfx->hideInventory();
+}
+
+void RivenExternal::xt7500_checkmarbles(uint16 argc, uint16 *argv) {
+ // TODO: Lots of stuff to do here, eventually we have to check each individual
+ // marble position and set apower based on that. The game handles the video playing
+ // so we don't have to. For the purposes of making the game progress further, we'll
+ // just turn the power on for now.
+ *_vm->matchVarToString("apower") = 1;
+}
+
+void RivenExternal::xt7600_setupmarbles(uint16 argc, uint16 *argv) {
+ // TODO: Marble puzzle related
+}
+
+void RivenExternal::xt7800_setup(uint16 argc, uint16 *argv) {
+ // TODO: Marble puzzle related
+}
+
+void RivenExternal::xdrawmarbles(uint16 argc, uint16 *argv) {
+ // TODO: Marble puzzle related
+}
+
+void RivenExternal::xtakeit(uint16 argc, uint16 *argv) {
+ // TODO: Marble puzzle related
+}
+
+void RivenExternal::xtscpbtn(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xtisland4990_domecheck(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xtisland5056_opencard(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xtisland5056_resetsliders(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xtisland5056_slidermd(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xtisland5056_slidermw(uint16 argc, uint16 *argv) {
+ // TODO: Dome related
+}
+
+void RivenExternal::xtatboundary(uint16 argc, uint16 *argv) {
+ runDemoBoundaryDialog();
+}
+
+// ------------------------------------------------------------------------------------
+// Common external commands
+// ------------------------------------------------------------------------------------
+
+void RivenExternal::xflies(uint16 argc, uint16 *argv) {
+ // TODO: Activate the "flies" effect
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/riven_external.h b/engines/mohawk/riven_external.h
new file mode 100644
index 0000000000..b3b9025d74
--- /dev/null
+++ b/engines/mohawk/riven_external.h
@@ -0,0 +1,252 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef RIVEN_EXTERNAL_H
+#define RIVEN_EXTERNAL_H
+
+#include "mohawk/riven.h"
+
+namespace Mohawk {
+
+#define COMMAND(x) _externalCommands.push_back(new RivenExternalCmd(#x, &RivenExternal::x))
+
+class RivenExternal {
+public:
+ RivenExternal(MohawkEngine_Riven *vm);
+ ~RivenExternal();
+
+ void runCommand(uint16 argc, uint16 *argv);
+
+private:
+ MohawkEngine_Riven *_vm;
+ Common::RandomSource *_rnd;
+
+ typedef void (RivenExternal::*ExternalCmd)(uint16 argc, uint16 *argv);
+
+ struct RivenExternalCmd {
+ RivenExternalCmd(const char *d, ExternalCmd p) : desc(d), proc(p) {}
+ const char *desc;
+ ExternalCmd proc;
+ };
+
+ Common::Array<RivenExternalCmd*> _externalCommands;
+ void setupCommands();
+
+ // Supplementary Functions
+ int jspitElevatorLoop();
+ void runDemoBoundaryDialog();
+ void runEndGame(uint16 video);
+
+ // -----------------------------------------------------
+ // aspit (Main Menu, Books, Setup) external commands
+ // Main Menu
+ void xastartupbtnhide(uint16 argc, uint16 *argv);
+ void xasetupcomplete(uint16 argc, uint16 *argv);
+ // Atrus' Journal
+ void xaatrusopenbook(uint16 argc, uint16 *argv);
+ void xaatrusbookback(uint16 argc, uint16 *argv);
+ void xaatrusbookprevpage(uint16 argc, uint16 *argv);
+ void xaatrusbooknextpage(uint16 argc, uint16 *argv);
+ // Catherine's Journal
+ void xacathopenbook(uint16 argc, uint16 *argv);
+ void xacathbookback(uint16 argc, uint16 *argv);
+ void xacathbookprevpage(uint16 argc, uint16 *argv);
+ void xacathbooknextpage(uint16 argc, uint16 *argv);
+ // Trap Book
+ void xtrapbookback(uint16 argc, uint16 *argv);
+ void xatrapbookclose(uint16 argc, uint16 *argv);
+ void xatrapbookopen(uint16 argc, uint16 *argv);
+ // aspit DVD-specific commands
+ void xarestoregame(uint16 argc, uint16 *argv);
+ // aspit Demo-specific commands
+ void xadisablemenureturn(uint16 argc, uint16 *argv);
+ void xaenablemenureturn(uint16 argc, uint16 *argv);
+ void xalaunchbrowser(uint16 argc, uint16 *argv);
+
+ // -----------------------------------------------------
+ // bspit (Boiler Island) external commands
+ // Gehn's Lab Journal
+ void xblabopenbook(uint16 argc, uint16 *argv);
+ void xblabbooknextpage(uint16 argc, uint16 *argv);
+ void xblabbookprevpage(uint16 argc, uint16 *argv);
+ // Boiler Puzzle
+ void xsoundplug(uint16 argc, uint16 *argv);
+ void xbchangeboiler(uint16 argc, uint16 *argv);
+ void xbupdateboiler(uint16 argc, uint16 *argv);
+ // Frog Trap
+ void xbsettrap(uint16 argc, uint16 *argv);
+ void xbcheckcatch(uint16 argc, uint16 *argv);
+ void xbait(uint16 argc, uint16 *argv);
+ void xbfreeytram(uint16 argc, uint16 *argv);
+ void xbaitplate(uint16 argc, uint16 *argv);
+ // Dome
+ void xbisland190_opencard(uint16 argc, uint16 *argv);
+ void xbisland190_resetsliders(uint16 argc, uint16 *argv);
+ void xbisland190_slidermd(uint16 argc, uint16 *argv);
+ void xbisland190_slidermw(uint16 argc, uint16 *argv);
+ void xbscpbtn(uint16 argc, uint16 *argv);
+ void xbisland_domecheck(uint16 argc, uint16 *argv);
+ // Water Control
+ void xvalvecontrol(uint16 argc, uint16 *argv);
+ // Run the Wood Chipper
+ void xbchipper(uint16 argc, uint16 *argv);
+
+ // -----------------------------------------------------
+ // gspit (Garden Island) external commands
+ // Pins
+ void xgresetpins(uint16 argc, uint16 *argv);
+ void xgrotatepins(uint16 argc, uint16 *argv);
+ void xgpincontrols(uint16 argc, uint16 *argv);
+ // Dome
+ void xgisland25_opencard(uint16 argc, uint16 *argv);
+ void xgisland25_resetsliders(uint16 argc, uint16 *argv);
+ void xgisland25_slidermd(uint16 argc, uint16 *argv);
+ void xgisland25_slidermw(uint16 argc, uint16 *argv);
+ void xgscpbtn(uint16 argc, uint16 *argv);
+ void xgisland1490_domecheck(uint16 argc, uint16 *argv);
+ // Mapping
+ void xgplateau3160_dopools(uint16 argc, uint16 *argv);
+ // Scribe Taking the Tram
+ void xgwt200_scribetime(uint16 argc, uint16 *argv);
+ void xgwt900_scribe(uint16 argc, uint16 *argv);
+ // Periscope/Prison Viewer
+ void xgplaywhark(uint16 argc, uint16 *argv);
+ void xgrviewer(uint16 argc, uint16 *argv);
+ void xgwharksnd(uint16 argc, uint16 *argv);
+ void xglview_prisonoff(uint16 argc, uint16 *argv);
+ void xglview_villageoff(uint16 argc, uint16 *argv);
+ void xglviewer(uint16 argc, uint16 *argv);
+ void xglview_prisonon(uint16 argc, uint16 *argv);
+ void xglview_villageon(uint16 argc, uint16 *argv);
+
+ // -----------------------------------------------------
+ // jspit (Jungle Island) external commands
+ // Rebel Tunnel Puzzle
+ void xreseticons(uint16 argc, uint16 *argv);
+ void xicon(uint16 argc, uint16 *argv);
+ void xcheckicons(uint16 argc, uint16 *argv);
+ void xtoggleicon(uint16 argc, uint16 *argv);
+ void xjtunnel103_pictfix(uint16 argc, uint16 *argv);
+ void xjtunnel104_pictfix(uint16 argc, uint16 *argv);
+ void xjtunnel105_pictfix(uint16 argc, uint16 *argv);
+ void xjtunnel106_pictfix(uint16 argc, uint16 *argv);
+ // Lower the gallows carriage
+ void xvga1300_carriage(uint16 argc, uint16 *argv);
+ // Dome
+ void xjdome25_resetsliders(uint16 argc, uint16 *argv);
+ void xjdome25_slidermd(uint16 argc, uint16 *argv);
+ void xjdome25_slidermw(uint16 argc, uint16 *argv);
+ void xjscpbtn(uint16 argc, uint16 *argv);
+ void xjisland3500_domecheck(uint16 argc, uint16 *argv);
+ // Whark Elevator
+ void xhandlecontroldown(uint16 argc, uint16 *argv);
+ void xhandlecontrolmid(uint16 argc, uint16 *argv);
+ void xhandlecontrolup(uint16 argc, uint16 *argv);
+ // Beetle
+ void xjplaybeetle_550(uint16 argc, uint16 *argv);
+ void xjplaybeetle_600(uint16 argc, uint16 *argv);
+ void xjplaybeetle_950(uint16 argc, uint16 *argv);
+ void xjplaybeetle_1050(uint16 argc, uint16 *argv);
+ void xjplaybeetle_1450(uint16 argc, uint16 *argv);
+ // Creatures in the Lagoon
+ void xjlagoon700_alert(uint16 argc, uint16 *argv);
+ void xjlagoon800_alert(uint16 argc, uint16 *argv);
+ void xjlagoon1500_alert(uint16 argc, uint16 *argv);
+ // Play the Whark Game
+ void xschool280_playwhark(uint16 argc, uint16 *argv);
+ // jspit Demo-specific commands
+ void xjatboundary(uint16 argc, uint16 *argv);
+
+ // -----------------------------------------------------
+ // ospit (233rd Age / Gehn's Office) external commands
+ // Death!
+ void xorollcredittime(uint16 argc, uint16 *argv);
+ // Trap Book Puzzle
+ void xbookclick(uint16 argc, uint16 *argv); // Four params -- movie_sref, start_time, end_time, u0
+ // Blank Linking Book
+ void xooffice30_closebook(uint16 argc, uint16 *argv);
+ // Gehn's Journal
+ void xobedroom5_closedrawer(uint16 argc, uint16 *argv);
+ void xogehnopenbook(uint16 argc, uint16 *argv);
+ void xogehnbookprevpage(uint16 argc, uint16 *argv);
+ void xogehnbooknextpage(uint16 argc, uint16 *argv);
+ // Elevator Combination
+ void xgwatch(uint16 argc, uint16 *argv);
+
+ // -----------------------------------------------------
+ // pspit (Prison Island) external commands
+ // Prison Elevator
+ void xpisland990_elevcombo(uint16 argc, uint16 *argv); // Param1: button
+ // Dome
+ void xpscpbtn(uint16 argc, uint16 *argv);
+ void xpisland290_domecheck(uint16 argc, uint16 *argv);
+ void xpisland25_opencard(uint16 argc, uint16 *argv);
+ void xpisland25_resetsliders(uint16 argc, uint16 *argv);
+ void xpisland25_slidermd(uint16 argc, uint16 *argv);
+ void xpisland25_slidermw(uint16 argc, uint16 *argv);
+
+ // -----------------------------------------------------
+ // rspit (Rebel Age / Tay) external commands
+ void xrcredittime(uint16 argc, uint16 *argv);
+ void xrshowinventory(uint16 argc, uint16 *argv);
+ void xrhideinventory(uint16 argc, uint16 *argv);
+ void xrwindowsetup(uint16 argc, uint16 *argv);
+
+ // -----------------------------------------------------
+ // tspit (Temple Island) external commands
+ // Telescope
+ void xtexterior300_telescopedown(uint16 argc, uint16 *argv);
+ void xtexterior300_telescopeup(uint16 argc, uint16 *argv);
+ // Called when clicking the telescope cover buttons. button is the button number (1...5).
+ void xtisland390_covercombo(uint16 argc, uint16 *argv); // Param1: button
+ // Atrus' Journal and Trap Book are added to inventory
+ void xtatrusgivesbooks(uint16 argc, uint16 *argv);
+ // Trap Book is removed from inventory
+ void xtchotakesbook(uint16 argc, uint16 *argv);
+ void xthideinventory(uint16 argc, uint16 *argv);
+ // Marble Puzzle
+ void xt7500_checkmarbles(uint16 argc, uint16 *argv);
+ void xt7600_setupmarbles(uint16 argc, uint16 *argv);
+ void xt7800_setup(uint16 argc, uint16 *argv);
+ void xdrawmarbles(uint16 argc, uint16 *argv);
+ void xtakeit(uint16 argc, uint16 *argv);
+ // Dome
+ void xtscpbtn(uint16 argc, uint16 *argv);
+ void xtisland4990_domecheck(uint16 argc, uint16 *argv);
+ void xtisland5056_opencard(uint16 argc, uint16 *argv);
+ void xtisland5056_resetsliders(uint16 argc, uint16 *argv);
+ void xtisland5056_slidermd(uint16 argc, uint16 *argv);
+ void xtisland5056_slidermw(uint16 argc, uint16 *argv);
+ // tspit Demo-specific commands
+ void xtatboundary(uint16 argc, uint16 *argv);
+
+ // -----------------------------------------------------
+ // Common external commands
+ void xflies(uint16 argc, uint16 *argv); // Start the "flies" realtime effect. u0 seems always 0, u1 is a small number (< 10).
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/riven_saveload.cpp b/engines/mohawk/riven_saveload.cpp
new file mode 100644
index 0000000000..c5cdcb6070
--- /dev/null
+++ b/engines/mohawk/riven_saveload.cpp
@@ -0,0 +1,390 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "mohawk/riven.h"
+#include "mohawk/riven_saveload.h"
+
+#include "common/util.h"
+
+namespace Mohawk {
+
+RivenSaveLoad::RivenSaveLoad(MohawkEngine_Riven *vm, Common::SaveFileManager *saveFileMan) : _vm(vm), _saveFileMan(saveFileMan) {
+ _loadFile = new MohawkFile();
+}
+
+RivenSaveLoad::~RivenSaveLoad() {
+ delete _loadFile;
+}
+
+Common::StringList RivenSaveLoad::generateSaveGameList() {
+ return _saveFileMan->listSavefiles("*.rvn");
+}
+
+// Note: The stack numbers we use do not match up to what the original executable,
+// so, match them ;)
+static uint16 mapOldStackIDToNew(uint16 oldID) {
+ switch (oldID) {
+ case 1:
+ return ospit;
+ case 2:
+ return pspit;
+ case 3:
+ return rspit;
+ case 4:
+ return tspit;
+ case 5:
+ return bspit;
+ case 6:
+ return gspit;
+ case 7:
+ return jspit;
+ case 8:
+ return aspit;
+ }
+ error ("Unknown old stack ID %d", oldID);
+ return 0;
+}
+
+static uint16 mapNewStackIDToOld(uint16 newID) {
+ switch (newID) {
+ case aspit:
+ return 8;
+ case bspit:
+ return 5;
+ case gspit:
+ return 6;
+ case jspit:
+ return 7;
+ case ospit:
+ return 1;
+ case pspit:
+ return 2;
+ case rspit:
+ return 3;
+ case tspit:
+ return 4;
+ }
+ error ("Unknown new stack ID %d", newID);
+ return 0;
+}
+
+bool RivenSaveLoad::loadGame(Common::String filename) {
+ if (_vm->getFeatures() & GF_DEMO) // Don't load games in the demo
+ return false;
+
+ Common::InSaveFile *loadFile;
+ if (!(loadFile = _saveFileMan->openForLoading(filename.c_str())))
+ return false;
+ debug (0, "Loading game from \'%s\'", filename.c_str());
+
+ _loadFile->open(loadFile);
+
+ // First, let's make sure we're using a saved game file from this version of Riven by checking the VERS resource
+ Common::SeekableReadStream *vers = _loadFile->getRawData(ID_VERS, 1);
+ uint32 saveGameVersion = vers->readUint32BE();
+ delete vers;
+ if ((saveGameVersion == kCDSaveGameVersion && (_vm->getFeatures() & GF_DVD))
+ || (saveGameVersion == kDVDSaveGameVersion && !(_vm->getFeatures() & GF_DVD))) {
+ warning ("Incompatible saved game versions. No support for this yet.");
+ delete _loadFile;
+ return false;
+ }
+
+ // Now, we'll read in the variable values.
+ Common::SeekableReadStream *vars = _loadFile->getRawData(ID_VARS, 1);
+ Common::Array<uint32> rawVariables;
+
+ while (!vars->eos()) {
+ vars->readUint32BE(); // Unknown (Stack?)
+ vars->readUint32BE(); // Unknown (0 or 1)
+ rawVariables.push_back(vars->readUint32BE());
+ }
+
+ delete vars;
+
+ // Next, we set the variables based on the name found by the index in the VARS resource.
+ // TODO: Merge with code in mohawk.cpp for loading names?
+ Common::SeekableReadStream *names = _loadFile->getRawData(ID_NAME, 1);
+
+ uint16 namesCount = names->readUint16BE();
+ uint16 *stringOffsets = new uint16[namesCount];
+ for (uint16 i = 0; i < namesCount; i++)
+ stringOffsets[i] = names->readUint16BE();
+ for (uint16 i = 0; i < namesCount; i++)
+ names->readUint16BE(); // Skip unknown values
+ uint32 curNamesPos = names->pos();
+
+ uint16 stackID = 0;
+ uint16 cardID = 0;
+
+ for (uint32 i = 0; i < rawVariables.size() && i < namesCount && !names->eos(); i++) {
+ names->seek(curNamesPos);
+ names->seek(stringOffsets[i], SEEK_CUR);
+
+ Common::String name = Common::String::emptyString;
+ char c = (char)names->readByte();
+
+ while (c) {
+ name += c;
+ c = (char)names->readByte();
+ }
+
+ uint32 *var = _vm->matchVarToString(name);
+
+ *var = rawVariables[i];
+
+ if (!scumm_stricmp(name.c_str(), "CurrentStackID"))
+ stackID = mapOldStackIDToNew(rawVariables[i]);
+ else if (!scumm_stricmp(name.c_str(), "CurrentCardID"))
+ cardID = rawVariables[i];
+ else if (!scumm_stricmp(name.c_str(), "ReturnStackID"))
+ *var = mapOldStackIDToNew(rawVariables[i]);
+ }
+
+ _vm->changeToStack(stackID);
+ _vm->changeToCard(cardID);
+
+ delete names;
+
+ // Reset zip mode data
+ _vm->_zipModeData.clear();
+
+ // Finally, we load in zip mode data.
+ Common::SeekableReadStream *zips = _loadFile->getRawData(ID_ZIPS, 1);
+ uint16 zipsRecordCount = zips->readUint16BE();
+ for (uint16 i = 0; i < zipsRecordCount; i++) {
+ ZipMode zip;
+ uint16 zipsNameLength = zips->readUint16BE();
+ for (uint16 j = 0; j < zipsNameLength; j++)
+ zip.name += zips->readByte();
+ zip.id = zips->readUint16BE();
+ _vm->_zipModeData.push_back(zip);
+ }
+ delete zips;
+
+ delete _loadFile;
+ _loadFile = NULL;
+ return true;
+}
+
+Common::MemoryWriteStreamDynamic *RivenSaveLoad::genVERSSection() {
+ Common::MemoryWriteStreamDynamic *stream = new Common::MemoryWriteStreamDynamic();
+ if (_vm->getFeatures() & GF_DVD)
+ stream->writeUint32BE(kDVDSaveGameVersion);
+ else
+ stream->writeUint32BE(kCDSaveGameVersion);
+ return stream;
+}
+
+Common::MemoryWriteStreamDynamic *RivenSaveLoad::genVARSSection() {
+ Common::MemoryWriteStreamDynamic *stream = new Common::MemoryWriteStreamDynamic();
+
+ for (uint32 i = 0; i < _vm->getVarCount(); i++) {
+ stream->writeUint32BE(0); // Unknown
+ stream->writeUint32BE(0); // Unknown
+ stream->writeUint32BE(_vm->getGlobalVar(i));
+ }
+
+ return stream;
+}
+
+Common::MemoryWriteStreamDynamic *RivenSaveLoad::genNAMESection() {
+ Common::MemoryWriteStreamDynamic *stream = new Common::MemoryWriteStreamDynamic();
+
+ stream->writeUint16BE((uint16)_vm->getVarCount());
+
+ uint16 curPos = 0;
+ for (uint16 i = 0; i < _vm->getVarCount(); i++) {
+ stream->writeUint16BE(curPos);
+ curPos += _vm->getGlobalVarName(i).size() + 1;
+ }
+
+ for (uint16 i = 0; i < _vm->getVarCount(); i++)
+ stream->writeUint16BE(i);
+
+ for (uint16 i = 0; i < _vm->getVarCount(); i++) {
+ stream->write(_vm->getGlobalVarName(i).c_str(), _vm->getGlobalVarName(i).size());
+ stream->writeByte(0);
+ }
+
+ return stream;
+}
+
+Common::MemoryWriteStreamDynamic *RivenSaveLoad::genZIPSSection() {
+ Common::MemoryWriteStreamDynamic *stream = new Common::MemoryWriteStreamDynamic();
+
+ stream->writeUint16BE(_vm->_zipModeData.size());
+
+ for (uint16 i = 0; i < _vm->_zipModeData.size(); i++) {
+ stream->writeUint16BE(_vm->_zipModeData[i].name.size());
+ stream->write(_vm->_zipModeData[i].name.c_str(), _vm->_zipModeData[i].name.size());
+ stream->writeUint16BE(_vm->_zipModeData[i].id);
+ }
+
+ return stream;
+}
+
+bool RivenSaveLoad::saveGame(Common::String filename) {
+ // Note, this code is still WIP. It works quite well for now.
+
+ // Make sure we have the right extension
+ if (!filename.hasSuffix(".rvn") && !filename.hasSuffix(".RVN"))
+ filename += ".rvn";
+
+ // Convert class variables to variable numbers
+ *_vm->matchVarToString("currentstackid") = mapNewStackIDToOld(_vm->getCurStack());
+ *_vm->matchVarToString("currentcardid") = _vm->getCurCard();
+ *_vm->matchVarToString("returnstackid") = mapNewStackIDToOld(*_vm->matchVarToString("returnstackid"));
+
+ Common::OutSaveFile *saveFile;
+ if (!(saveFile = _saveFileMan->openForSaving(filename.c_str())))
+ return false;
+ debug (0, "Saving game to \'%s\'", filename.c_str());
+
+ Common::MemoryWriteStreamDynamic *versSection = genVERSSection();
+ Common::MemoryWriteStreamDynamic *nameSection = genNAMESection();
+ Common::MemoryWriteStreamDynamic *varsSection = genVARSSection();
+ Common::MemoryWriteStreamDynamic *zipsSection = genZIPSSection();
+
+ // Let's calculate the file size!
+ uint32 fileSize = 0;
+ fileSize += versSection->size();
+ fileSize += nameSection->size();
+ fileSize += varsSection->size();
+ fileSize += zipsSection->size();
+ fileSize += 16; // RSRC Header
+ fileSize += 4; // Type Table Header
+ fileSize += 4 * 8; // Type Table Entries
+ fileSize += 2; // Pseudo-Name entries
+
+ // IFF Header
+ saveFile->writeUint32BE(ID_MHWK);
+ saveFile->writeUint32BE(fileSize);
+
+ // RSRC Header
+ saveFile->writeUint32BE(ID_RSRC);
+ saveFile->writeUint32BE(16); // Size of RSRC
+ saveFile->writeUint32BE(fileSize + 8); // Add on the 8 from the IFF header
+ saveFile->writeUint32BE(28); // IFF + RSRC
+ saveFile->writeUint16BE(62); // File Table Offset
+ saveFile->writeUint16BE(44); // 4 + 4 * 10
+
+ //Type Table
+ saveFile->writeUint16BE(36); // After the Type Table Entries
+ saveFile->writeUint16BE(4); // 4 Type Table Entries
+
+ // Hardcode Entries
+ saveFile->writeUint32BE(ID_VERS);
+ saveFile->writeUint16BE(38);
+ saveFile->writeUint16BE(36);
+
+ saveFile->writeUint32BE(ID_NAME);
+ saveFile->writeUint16BE(44);
+ saveFile->writeUint16BE(36);
+
+ saveFile->writeUint32BE(ID_VARS);
+ saveFile->writeUint16BE(50);
+ saveFile->writeUint16BE(36);
+
+ saveFile->writeUint32BE(ID_ZIPS);
+ saveFile->writeUint16BE(56);
+ saveFile->writeUint16BE(36);
+
+ // Pseudo-Name Table/Name List
+ saveFile->writeUint16BE(0); // We don't need a name list
+
+ // VERS Section (Resource Table)
+ saveFile->writeUint16BE(1);
+ saveFile->writeUint16BE(1);
+ saveFile->writeUint16BE(1);
+
+ // NAME Section (Resource Table)
+ saveFile->writeUint16BE(1);
+ saveFile->writeUint16BE(1);
+ saveFile->writeUint16BE(2);
+
+ // VARS Section (Resource Table)
+ saveFile->writeUint16BE(1);
+ saveFile->writeUint16BE(1);
+ saveFile->writeUint16BE(3);
+
+ // ZIPS Section (Resource Table)
+ saveFile->writeUint16BE(1);
+ saveFile->writeUint16BE(1);
+ saveFile->writeUint16BE(4);
+
+ // File Table
+ saveFile->writeUint32BE(4);
+
+ // VERS Section (File Table)
+ saveFile->writeUint32BE(134);
+ saveFile->writeUint16BE(versSection->size() & 0xFFFF);
+ saveFile->writeByte((versSection->size() & 0xFF0000) >> 16);
+ saveFile->writeByte(0);
+ saveFile->writeUint16BE(0);
+
+ // NAME Section (File Table)
+ saveFile->writeUint32BE(134 + versSection->size());
+ saveFile->writeUint16BE(nameSection->size() & 0xFFFF);
+ saveFile->writeByte((nameSection->size() & 0xFF0000) >> 16);
+ saveFile->writeByte(0);
+ saveFile->writeUint16BE(0);
+
+ // VARS Section (File Table)
+ saveFile->writeUint32BE(134 + versSection->size() + nameSection->size());
+ saveFile->writeUint16BE(varsSection->size() & 0xFFFF);
+ saveFile->writeByte((varsSection->size() & 0xFF0000) >> 16);
+ saveFile->writeByte(0);
+ saveFile->writeUint16BE(0);
+
+ // ZIPS Section (File Table)
+ saveFile->writeUint32BE(134 + versSection->size() + nameSection->size() + varsSection->size());
+ saveFile->writeUint16BE(zipsSection->size() & 0xFFFF);
+ saveFile->writeByte((zipsSection->size() & 0xFF0000) >> 16);
+ saveFile->writeByte(0);
+ saveFile->writeUint16BE(0);
+
+ saveFile->write(versSection->getData(), versSection->size());
+ saveFile->write(nameSection->getData(), nameSection->size());
+ saveFile->write(varsSection->getData(), varsSection->size());
+ saveFile->write(zipsSection->getData(), zipsSection->size());
+
+ saveFile->finalize();
+
+ delete saveFile;
+ delete versSection;
+ delete nameSection;
+ delete varsSection;
+ delete zipsSection;
+
+ return true;
+}
+
+void RivenSaveLoad::deleteSave(Common::String saveName) {
+ debug (0, "Deleting save file \'%s\'", saveName.c_str());
+ _saveFileMan->removeSavefile(saveName.c_str());
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/riven_saveload.h b/engines/mohawk/riven_saveload.h
new file mode 100644
index 0000000000..b59baac9ba
--- /dev/null
+++ b/engines/mohawk/riven_saveload.h
@@ -0,0 +1,66 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MOHAWK_SAVELOAD_H
+#define MOHAWK_SAVELOAD_H
+
+#include "common/savefile.h"
+#include "common/str.h"
+
+#include "mohawk/file.h"
+
+namespace Mohawk {
+
+class MohawkEngine_Riven;
+
+enum {
+ kCDSaveGameVersion = 0x00010000,
+ kDVDSaveGameVersion = 0x00010100
+};
+
+class RivenSaveLoad {
+public:
+ RivenSaveLoad(MohawkEngine_Riven*, Common::SaveFileManager*);
+ ~RivenSaveLoad();
+
+ Common::StringList generateSaveGameList();
+ bool loadGame(Common::String);
+ bool saveGame(Common::String);
+ void deleteSave(Common::String);
+
+private:
+ MohawkEngine_Riven *_vm;
+ Common::SaveFileManager *_saveFileMan;
+ MohawkFile *_loadFile;
+
+ Common::MemoryWriteStreamDynamic *genVERSSection();
+ Common::MemoryWriteStreamDynamic *genNAMESection();
+ Common::MemoryWriteStreamDynamic *genVARSSection();
+ Common::MemoryWriteStreamDynamic *genZIPSSection();
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/riven_scripts.cpp b/engines/mohawk/riven_scripts.cpp
new file mode 100644
index 0000000000..e04b2a6e3d
--- /dev/null
+++ b/engines/mohawk/riven_scripts.cpp
@@ -0,0 +1,618 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "mohawk/graphics.h"
+#include "mohawk/riven.h"
+#include "mohawk/riven_external.h"
+#include "mohawk/riven_scripts.h"
+
+#include "common/stream.h"
+#include "graphics/cursorman.h"
+
+namespace Mohawk {
+
+RivenScript::RivenScript(MohawkEngine_Riven *vm, Common::SeekableReadStream *stream, uint16 scriptType)
+ : _vm(vm), _stream(stream), _scriptType(scriptType) {
+ setupOpcodes();
+}
+
+RivenScript::~RivenScript() {
+ delete _stream;
+}
+
+RivenScriptList RivenScript::readScripts(MohawkEngine_Riven *vm, Common::SeekableReadStream *stream) {
+ RivenScriptList scriptList;
+
+ uint16 scriptCount = stream->readUint16BE();
+ for (uint16 i = 0; i < scriptCount; i++) {
+ uint16 scriptType = stream->readUint16BE();
+ uint32 scriptSize = calculateScriptSize(stream);
+ scriptList.push_back(Common::SharedPtr<RivenScript>(new RivenScript(vm, stream->readStream(scriptSize), scriptType)));
+ }
+
+ return scriptList;
+}
+
+uint32 RivenScript::calculateCommandSize(Common::SeekableReadStream* script) {
+ uint16 command = script->readUint16BE();
+ uint32 commandSize = 2;
+ if (command == 8) {
+ if (script->readUint16BE() != 2)
+ warning ("if-then-else unknown value is not 2");
+ script->readUint16BE(); // variable to check against
+ uint16 logicBlockCount = script->readUint16BE(); // number of logic blocks
+ commandSize += 6; // 2 + variable + logicBlocks
+
+ for (uint16 i = 0; i < logicBlockCount; i++) {
+ script->readUint16BE(); // Block variable
+ uint16 logicBlockLength = script->readUint16BE();
+ commandSize += 4;
+ for (uint16 j = 0; j < logicBlockLength; j++)
+ commandSize += calculateCommandSize(script);
+ }
+ } else {
+ uint16 argCount = script->readUint16BE();
+ commandSize += 2;
+ for (uint16 i = 0; i < argCount; i++) {
+ script->readUint16BE();
+ commandSize += 2;
+ }
+ }
+
+ return commandSize;
+}
+
+uint32 RivenScript::calculateScriptSize(Common::SeekableReadStream* script) {
+ uint32 oldPos = script->pos();
+ uint16 commandCount = script->readUint16BE();
+ uint16 scriptSize = 2; // 2 for command count
+
+ for (uint16 i = 0; i < commandCount; i++)
+ scriptSize += calculateCommandSize(script);
+
+ script->seek(oldPos);
+ return scriptSize;
+}
+
+#define OPCODE(x) { &RivenScript::x, #x }
+
+void RivenScript::setupOpcodes() {
+ static const RivenOpcode riven_opcodes[] = {
+ // 0x00 (0 decimal)
+ OPCODE(empty),
+ OPCODE(drawBitmap),
+ OPCODE(switchCard),
+ OPCODE(playScriptSLST),
+ // 0x04 (4 decimal)
+ OPCODE(playSound),
+ OPCODE(empty), // Empty
+ OPCODE(empty), // Complex animation (not used)
+ OPCODE(setVariable),
+ // 0x08 (8 decimal)
+ OPCODE(mohawkSwitch),
+ OPCODE(enableHotspot),
+ OPCODE(disableHotspot),
+ OPCODE(empty), // Empty
+ // 0x0C (12 decimal)
+ OPCODE(clearSLST),
+ OPCODE(changeCursor),
+ OPCODE(delay),
+ OPCODE(empty), // Empty
+ // 0x10 (16 decimal)
+ OPCODE(empty), // Empty
+ OPCODE(runExternalCommand),
+ OPCODE(transition),
+ OPCODE(refreshCard),
+ // 0x14 (20 decimal)
+ OPCODE(disableScreenUpdate),
+ OPCODE(enableScreenUpdate),
+ OPCODE(empty), // Empty
+ OPCODE(empty), // Empty
+ // 0x18 (24 decimal)
+ OPCODE(incrementVariable),
+ OPCODE(empty), // Empty
+ OPCODE(empty), // Empty
+ OPCODE(changeStack),
+ // 0x1C (28 decimal)
+ OPCODE(disableMovie),
+ OPCODE(disableAllMovies),
+ OPCODE(empty), // Set movie rate (not used)
+ OPCODE(enableMovie),
+ // 0x20 (32 decimal)
+ OPCODE(playMovie),
+ OPCODE(playMovieBg),
+ OPCODE(stopMovie),
+ OPCODE(empty), // Start a water effect (not used)
+ // 0x24 (36 decimal)
+ OPCODE(unk_36), // Unknown
+ OPCODE(fadeAmbientSounds),
+ OPCODE(complexPlayMovie),
+ OPCODE(activatePLST),
+ // 0x28 (40 decimal)
+ OPCODE(activateSLST),
+ OPCODE(activateMLSTAndPlay),
+ OPCODE(empty), // Empty
+ OPCODE(activateBLST),
+ // 0x2C (44 decimal)
+ OPCODE(activateFLST),
+ OPCODE(zipMode),
+ OPCODE(activateMLST),
+ OPCODE(activateSLSTWithVolume)
+ };
+
+ _opcodes = riven_opcodes;
+}
+
+static void printTabs(byte tabs) {
+ for (byte i = 0; i < tabs; i++)
+ printf ("\t");
+}
+
+void RivenScript::dumpScript(Common::StringList varNames, Common::StringList xNames, byte tabs) {
+ if (_stream->pos() != 0)
+ _stream->seek(0);
+
+ printTabs(tabs); printf ("Stream Type %d:\n", _scriptType);
+ dumpCommands(varNames, xNames, tabs + 1);
+}
+
+void RivenScript::dumpCommands(Common::StringList varNames, Common::StringList xNames, byte tabs) {
+ uint16 commandCount = _stream->readUint16BE();
+
+ for (uint16 i = 0; i < commandCount; i++) {
+ uint16 command = _stream->readUint16BE();
+
+ if (command == 8) { // "Switch" Statement
+ if (_stream->readUint16BE() != 2)
+ warning ("if-then-else unknown value is not 2");
+ uint16 var = _stream->readUint16BE();
+ printTabs(tabs); printf("switch (%s) {\n", varNames[var].c_str());
+ uint16 logicBlockCount = _stream->readUint16BE();
+ for (uint16 j = 0; j < logicBlockCount; j++) {
+ uint16 varCheck = _stream->readUint16BE();
+ printTabs(tabs + 1);
+ if (varCheck == 0xFFFF)
+ printf("default:\n");
+ else
+ printf("case %d:\n", varCheck);
+ dumpCommands(varNames, xNames, tabs + 2);
+ printTabs(tabs + 2); printf("break;\n");
+ }
+ printTabs(tabs); printf("}\n");
+ } else if (command == 7) { // Use the variable name
+ _stream->readUint16BE(); // Skip the opcode var count
+ printTabs(tabs);
+ uint16 var = _stream->readUint16BE();
+ printf("%s = %d;\n", varNames[var].c_str(), _stream->readUint16BE());
+ } else if (command == 17) { // Use the external command name
+ _stream->readUint16BE(); // Skip the opcode var count
+ printTabs(tabs);
+ printf("%s(", xNames[_stream->readUint16BE()].c_str());
+ uint16 varCount = _stream->readUint16BE();
+ for (uint16 j = 0; j < varCount; j++) {
+ printf("%d", _stream->readUint16BE());
+ if (j != varCount - 1)
+ printf(", ");
+ }
+ printf (");\n");
+ } else if (command == 24) { // Use the variable name
+ _stream->readUint16BE(); // Skip the opcode var count
+ printTabs(tabs);
+ uint16 var = _stream->readUint16BE();
+ printf ("%s += %d;\n", varNames[var].c_str(), _stream->readUint16BE());
+ } else {
+ printTabs(tabs);
+ uint16 varCount = _stream->readUint16BE();
+ printf("%s(", _opcodes[command].desc);
+ for (uint16 j = 0; j < varCount; j++) {
+ printf("%d", _stream->readUint16BE());
+ if (j != varCount - 1)
+ printf(", ");
+ }
+ printf(");\n");
+ }
+ }
+}
+
+void RivenScript::runScript() {
+ if (_stream->pos() != 0)
+ _stream->seek(0);
+
+ processCommands(true);
+}
+
+void RivenScript::processCommands(bool runCommands) {
+ bool anotherBlockEvaluated = false;
+ bool runBlock = true;
+
+ uint16 commandCount = _stream->readUint16BE();
+
+ for (uint16 j = 0; j < commandCount && !_vm->shouldQuit() && _stream->pos() < _stream->size(); j++) {
+ uint16 command = _stream->readUint16BE();
+
+ if (command == 8) {
+ // Command 8 contains a conditional branch, similar to switch statements
+ if (_stream->readUint16BE() != 2)
+ warning("if-then-else unknown value is not 2");
+ uint16 var = _stream->readUint16BE(); // variable to check against
+ uint16 logicBlockCount = _stream->readUint16BE(); // number of logic blocks
+
+ for (uint16 k = 0; k < logicBlockCount; k++) {
+ uint16 checkValue = _stream->readUint16BE(); // variable for this logic block
+
+ // Run the following block if the block's variable is equal to the variable to check against
+ // Don't run it if the parent block is not executed
+ // And don't run it if another block has already evaluated to true (needed for the default case)
+ runBlock = (*_vm->getLocalVar(var) == checkValue || checkValue == 0xffff) && runCommands && !anotherBlockEvaluated;
+ processCommands(runBlock);
+
+ if (runBlock)
+ anotherBlockEvaluated = true;
+ }
+
+ anotherBlockEvaluated = false;
+ } else {
+ uint16 argCount = _stream->readUint16BE();
+ uint16 *argValues = new uint16[argCount];
+
+ for (uint16 k = 0; k < argCount; k++)
+ argValues[k] = _stream->readUint16BE();
+
+ if (runCommands) {
+ debug (4, "Running opcode %04x, argument count %d", command, argCount);
+ (this->*(_opcodes[command].proc)) (command, argCount, argValues);
+ }
+
+ delete[] argValues;
+ }
+ }
+}
+
+////////////////////////////////
+// Opcodes
+////////////////////////////////
+
+// Command 1: draw tBMP resource (tbmp_id, left, top, right, bottom, u0, u1, u2, u3)
+void RivenScript::drawBitmap(uint16 op, uint16 argc, uint16 *argv) {
+ if (argc < 5) {
+ // Copy the image to the whole screen, ignoring the rest of the parameters
+ _vm->_gfx->copyImageToScreen(argv[0], 0, 0, 608, 392);
+ } else {
+ // Copy the image to a certain part of the screen
+ _vm->_gfx->copyImageToScreen(argv[0], argv[1], argv[2], argv[3], argv[4]);
+ }
+
+ // Now, update the screen
+ _vm->_gfx->updateScreen();
+}
+
+// Command 2: go to card (card id)
+void RivenScript::switchCard(uint16 op, uint16 argc, uint16 *argv) {
+ _vm->changeToCard(argv[0]);
+}
+
+// Command 3: play an SLST from the script
+void RivenScript::playScriptSLST(uint16 op, uint16 argc, uint16 *argv) {
+ SLSTRecord slstRecord;
+ int offset = 0, j = 0;
+
+ slstRecord.index = 0; // not set by the scripts, so we set it to 0
+ slstRecord.sound_count = argv[0];
+ slstRecord.sound_ids = new uint16[slstRecord.sound_count];
+
+ offset = slstRecord.sound_count;
+
+ for (j = 0; j < slstRecord.sound_count; j++)
+ slstRecord.sound_ids[j] = argv[offset++];
+ slstRecord.fade_flags = argv[offset++];
+ slstRecord.loop = argv[offset++];
+ slstRecord.global_volume = argv[offset++];
+ slstRecord.u0 = argv[offset++];
+ slstRecord.u1 = argv[offset++];
+
+ slstRecord.volumes = new uint16[slstRecord.sound_count];
+ slstRecord.balances = new int16[slstRecord.sound_count];
+ slstRecord.u2 = new uint16[slstRecord.sound_count];
+
+ for (j = 0; j < slstRecord.sound_count; j++)
+ slstRecord.volumes[j] = argv[offset++];
+
+ for (j = 0; j < slstRecord.sound_count; j++)
+ slstRecord.balances[j] = argv[offset++]; // negative = left, 0 = center, positive = right
+
+ for (j = 0; j < slstRecord.sound_count; j++)
+ slstRecord.u2[j] = argv[offset++]; // Unknown
+
+ // Play the requested sound list
+ _vm->_sound->playSLST(slstRecord);
+ _vm->_activatedSLST = true;
+}
+
+// Command 4: play local tWAV resource (twav_id, volume, u1)
+void RivenScript::playSound(uint16 op, uint16 argc, uint16 *argv) {
+ _vm->_sound->playSound(argv[0], false);
+}
+
+// Command 7: set variable value (variable, value)
+void RivenScript::setVariable(uint16 op, uint16 argc, uint16 *argv) {
+ debug(2, "Setting variable %d to %d", argv[0], argv[1]);
+ *_vm->getLocalVar(argv[0]) = argv[1];
+}
+
+// Command 8: conditional branch
+void RivenScript::mohawkSwitch(uint16 op, uint16 argc, uint16 *argv) {
+ // dummy function, this opcode does logic checking in processCommands()
+}
+
+// Command 9: enable hotspot (blst_id)
+void RivenScript::enableHotspot(uint16 op, uint16 argc, uint16 *argv) {
+ for (uint16 i = 0; i < _vm->getHotspotCount(); i++) {
+ if (_vm->_hotspots[i].blstID == argv[0]) {
+ debug(2, "Enabling hotspot with BLST ID %d", argv[0]);
+ _vm->_hotspots[i].enabled = true;
+ }
+ }
+}
+
+// Command 10: disable hotspot (blst_id)
+void RivenScript::disableHotspot(uint16 op, uint16 argc, uint16 *argv) {
+ for (uint16 i = 0; i < _vm->getHotspotCount(); i++) {
+ if (_vm->_hotspots[i].blstID == argv[0]) {
+ debug(2, "Disabling hotspot with BLST ID %d", argv[0]);
+ _vm->_hotspots[i].enabled = false;
+ }
+ }
+}
+
+// Command 12: clear slst records (flags)
+void RivenScript::clearSLST(uint16 op, uint16 argc, uint16 *argv) {
+ warning ("STUB: clearSLST: Fade Out = %s, Fade In = %s", ((argv[0] & 1) != 0) ? "Yes" : "No", ((argv[0] & 2) != 0) ? "Yes" : "No");
+ //_vm->_sound->clearAllSLST();
+}
+
+// Command 13: set mouse cursor (cursor_id)
+void RivenScript::changeCursor(uint16 op, uint16 argc, uint16 *argv) {
+ debug(2, "Change to cursor %d", argv[0]);
+ _vm->_gfx->changeCursor(argv[0]);
+}
+
+// Command 14: pause script execution (delay in ms, u1)
+void RivenScript::delay(uint16 op, uint16 argc, uint16 *argv) {
+ debug(2, "Delay %dms", argv[0]);
+ if (argv[0] > 0)
+ _vm->_system->delayMillis(argv[0]);
+}
+
+// Command 17: call external command
+void RivenScript::runExternalCommand(uint16 op, uint16 argc, uint16 *argv) {
+ _vm->_externalScriptHandler->runCommand(argc, argv);
+}
+
+// Command 18: transition
+// Note that this opcode has 1 or 5 parameters, depending on parameter 0
+// Parameter 0: transition type
+// Parameters 1-4: transition rectangle
+void RivenScript::transition(uint16 op, uint16 argc, uint16 *argv) {
+ if (argc == 1) {
+ _vm->_gfx->scheduleTransition(argv[0]);
+ } else {
+ _vm->_gfx->scheduleTransition(argv[0], Common::Rect(argv[1], argv[2], argv[3], argv[4]));
+ }
+}
+
+// Command 19: reload card
+void RivenScript::refreshCard(uint16 op, uint16 argc, uint16 *argv) {
+ debug(2, "Reloading card");
+ _vm->changeToCard();
+}
+
+// Command 20: disable screen update
+void RivenScript::disableScreenUpdate(uint16 op, uint16 argc, uint16 *argv) {
+ debug(2, "Screen update disabled");
+ _vm->_gfx->_updatesEnabled = false;
+}
+
+// Command 21: enable screen update
+void RivenScript::enableScreenUpdate(uint16 op, uint16 argc, uint16 *argv) {
+ debug(2, "Screen update enabled");
+ _vm->_gfx->_updatesEnabled = true;
+ _vm->_gfx->updateScreen();
+}
+
+// Command 24: increment variable (variable, value)
+void RivenScript::incrementVariable(uint16 op, uint16 argc, uint16 *argv) {
+ uint32 *localVar = _vm->getLocalVar(argv[0]);
+ *localVar += argv[1];
+ debug (2, "Incrementing variable %d by %d, variable now is equal to %d", argv[0], argv[1], *localVar);
+}
+
+// Command 27: go to stack (stack_name code_hi code_lo)
+void RivenScript::changeStack(uint16 op, uint16 argc, uint16 *argv) {
+ Common::String stackName = _vm->getName(StackNames, argv[0]);
+ int8 index = -1;
+
+ for (byte i = 0; i < 8; i++)
+ if (!scumm_stricmp(_vm->getStackName(i).c_str(), stackName.c_str())) {
+ index = i;
+ break;
+ }
+
+ if (index == -1)
+ error ("\'%s\' is not a stack name!", stackName.c_str());
+
+ _vm->changeToStack(index);
+ uint32 rmapCode = (argv[1] << 16) + argv[2];
+ uint16 cardID = _vm->matchRMAPToCard(rmapCode);
+ _vm->changeToCard(cardID);
+}
+
+// Command 28: disable a movie
+void RivenScript::disableMovie(uint16 op, uint16 argc, uint16 *argv) {
+ _vm->_video->disableMovie(argv[0]);
+}
+
+// Command 29: disable all movies
+void RivenScript::disableAllMovies(uint16 op, uint16 argc, uint16 *argv) {
+ _vm->_video->disableAllMovies();
+}
+
+// Command 31: enable a movie
+void RivenScript::enableMovie(uint16 op, uint16 argc, uint16 *argv) {
+ _vm->_video->enableMovie(argv[0]);
+}
+
+// Command 32: play foreground movie - blocking (movie_id)
+void RivenScript::playMovie(uint16 op, uint16 argc, uint16 *argv) {
+ CursorMan.showMouse(false); // Hide the cursor before playing the video
+ _vm->_video->enableMovie(argv[0]);
+ _vm->_video->playMovieBlocking(argv[0]);
+ CursorMan.showMouse(true); // Show the cursor again when we're done ;)
+}
+
+// Command 33: play background movie - nonblocking (movie_id)
+void RivenScript::playMovieBg(uint16 op, uint16 argc, uint16 *argv) {
+ _vm->_video->enableMovie(argv[0]);
+ _vm->_video->playMovie(argv[0]);
+}
+
+// Command 34: stop a movie
+void RivenScript::stopMovie(uint16 op, uint16 argc, uint16 *argv) {
+ _vm->_video->disableMovie(argv[0]);
+ _vm->_video->stopMovie(argv[0]);
+}
+
+// Command 36: unknown
+void RivenScript::unk_36(uint16 op, uint16 argc, uint16 *argv) {
+ debug(0, "unk_36: Ignoring");
+}
+
+// Command 37: fade ambient sounds
+void RivenScript::fadeAmbientSounds(uint16 op, uint16 argc, uint16 *argv) {
+ warning("STUB: fadeAmbientSounds()");
+}
+
+// Command 38: Play a movie with extra parameters (movie id, delay high, delay low, record type, record id)
+void RivenScript::complexPlayMovie(uint16 op, uint16 argc, uint16 *argv) {
+ warning("STUB: complexPlayMovie");
+ printf ("\tMovie ID = %d\n", argv[0]);
+ printf ("\tDelay = %d\n", (argv[1] << 16) + argv[2]);
+ if (argv[3] == 0) {
+ printf ("\tDraw PLST %d\n", argv[4]);
+ } else if (argv[3] == 40) {
+ printf ("\tPlay SLST %d\n", argv[4]);
+ } else {
+ error ("Unknown complexPlayMovie record type %d", argv[3]);
+ }
+}
+
+// Command 39: activate PLST record (card picture lists)
+void RivenScript::activatePLST(uint16 op, uint16 argc, uint16 *argv) {
+ _vm->_gfx->drawPLST(argv[0]);
+
+ // An update is automatically sent here as long as it's not a load or update script and updates are enabled.
+ if (_scriptType != kCardLoadScript && _scriptType != kCardUpdateScript)
+ _vm->_gfx->updateScreen();
+}
+
+// Command 40: activate SLST record (card ambient sound lists)
+void RivenScript::activateSLST(uint16 op, uint16 argc, uint16 *argv) {
+ // WORKAROUND: Disable the SLST that is played during Riven's intro.
+ // Riven X does this too (spoke this over with Jeff)
+ if (_vm->getCurStack() == tspit && _vm->getCurCard() == 155 && argv[0] == 2)
+ return;
+
+ _vm->_sound->playSLST(argv[0], _vm->getCurCard());
+ _vm->_activatedSLST = true;
+}
+
+// Command 41: activate MLST record and play
+void RivenScript::activateMLSTAndPlay(uint16 op, uint16 argc, uint16 *argv) {
+ _vm->_video->enableMovie(argv[0] - 1);
+ _vm->_video->activateMLST(argv[0], _vm->getCurCard());
+ // TODO: Play movie (blocking?)
+}
+
+// Command 43: activate BLST record (card hotspot enabling lists)
+void RivenScript::activateBLST(uint16 op, uint16 argc, uint16 *argv) {
+ Common::SeekableReadStream* blst = _vm->getRawData(ID_BLST, _vm->getCurCard());
+ uint16 recordCount = blst->readUint16BE();
+
+ for (uint16 i = 0; i < recordCount; i++) {
+ uint16 index = blst->readUint16BE(); // record index
+ uint16 enabled = blst->readUint16BE();
+ uint16 hotspotID = blst->readUint16BE();
+
+ if (argv[0] == index)
+ for (uint16 j = 0; j < _vm->getHotspotCount(); j++)
+ if (_vm->_hotspots[j].blstID == hotspotID)
+ _vm->_hotspots[j].enabled = (enabled == 1);
+ }
+
+ delete blst;
+}
+
+// Command 44: activate FLST record (information on which SFXE resource this card should use)
+void RivenScript::activateFLST(uint16 op, uint16 argc, uint16 *argv) {
+ Common::SeekableReadStream* flst = _vm->getRawData(ID_FLST, _vm->getCurCard());
+ uint16 recordCount = flst->readUint16BE();
+
+ for (uint16 i = 0; i < recordCount; i++) {
+ uint16 index = flst->readUint16BE();
+ uint16 sfxeID = flst->readUint16BE();
+ if(flst->readUint16BE() != 0)
+ warning("FLST u0 non-zero");
+
+ if (index == argv[0]) {
+ _vm->_gfx->scheduleWaterEffect(sfxeID);
+ break;
+ }
+ }
+
+ delete flst;
+}
+
+// Command 45: do zip mode
+void RivenScript::zipMode(uint16 op, uint16 argc, uint16 *argv) {
+ // Check the ZIPS records to see if we have a match to the hotspot name
+ Common::String hotspotName = _vm->getHotspotName(_vm->getCurHotspot());
+
+ for (uint16 i = 0; i < _vm->_zipModeData.size(); i++)
+ if (_vm->_zipModeData[i].name == hotspotName) {
+ _vm->changeToCard(_vm->_zipModeData[i].id);
+ return;
+ }
+}
+
+// Command 46: activate MLST record (movie lists)
+void RivenScript::activateMLST(uint16 op, uint16 argc, uint16 *argv) {
+ _vm->_video->activateMLST(argv[0], _vm->getCurCard());
+}
+
+// Command 47: activate SLST record with a volume argument
+void RivenScript::activateSLSTWithVolume(uint16 op, uint16 argc, uint16 *argv) {
+ warning("STUB: activateSLSTWithVolume()");
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/riven_scripts.h b/engines/mohawk/riven_scripts.h
new file mode 100644
index 0000000000..36fce259f0
--- /dev/null
+++ b/engines/mohawk/riven_scripts.h
@@ -0,0 +1,127 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef RIVEN_SCRIPTS_H
+#define RIVEN_SCRIPTS_H
+
+class MohawkEngine_Riven;
+
+#define DECLARE_OPCODE(x) void x(uint16 op, uint16 argc, uint16 *argv)
+
+namespace Mohawk {
+// Script Types
+enum {
+ kMouseDownScript = 0,
+ kMouseDownScriptAlt = 1,
+ kMouseUpScript = 2,
+ kMouseMovedPressedReleasedScript = 3,
+ kMouseInsideScript = 4,
+ kMouseLeaveScript = 5, // This is unconfirmed
+
+ kCardLoadScript = 6,
+ kCardLeaveScript = 7,
+ kCardOpenScript = 9,
+ kCardUpdateScript = 10
+};
+
+class RivenScript;
+typedef Common::Array<Common::SharedPtr<RivenScript> > RivenScriptList;
+
+class RivenScript {
+public:
+ RivenScript(MohawkEngine_Riven *vm, Common::SeekableReadStream *stream, uint16 scriptType);
+ ~RivenScript();
+
+ void runScript();
+ void dumpScript(Common::StringList varNames, Common::StringList xNames, byte tabs);
+ uint16 getScriptType() { return _scriptType; }
+
+ // Read in an array of script objects from a stream
+ static RivenScriptList readScripts(MohawkEngine_Riven *vm, Common::SeekableReadStream *stream);
+
+private:
+ typedef void (RivenScript::*OpcodeProcRiven)(uint16 op, uint16 argc, uint16 *argv);
+ struct RivenOpcode {
+ OpcodeProcRiven proc;
+ const char *desc;
+ };
+ const RivenOpcode* _opcodes;
+ void setupOpcodes();
+
+ MohawkEngine_Riven *_vm;
+ Common::SeekableReadStream *_stream;
+ uint16 _scriptType;
+
+ void dumpCommands(Common::StringList varNames, Common::StringList xNames, byte tabs);
+ void processCommands(bool runCommands);
+
+ static uint32 calculateCommandSize(Common::SeekableReadStream* script);
+ static uint32 calculateScriptSize(Common::SeekableReadStream* script);
+
+ DECLARE_OPCODE(empty) { warning ("Unknown Opcode %04x", op); }
+
+ //Opcodes
+ DECLARE_OPCODE(drawBitmap);
+ DECLARE_OPCODE(switchCard);
+ DECLARE_OPCODE(playScriptSLST);
+ DECLARE_OPCODE(playSound);
+ DECLARE_OPCODE(setVariable);
+ DECLARE_OPCODE(mohawkSwitch);
+ DECLARE_OPCODE(enableHotspot);
+ DECLARE_OPCODE(disableHotspot);
+ DECLARE_OPCODE(clearSLST);
+ DECLARE_OPCODE(changeCursor);
+ DECLARE_OPCODE(delay);
+ DECLARE_OPCODE(runExternalCommand);
+ DECLARE_OPCODE(transition);
+ DECLARE_OPCODE(refreshCard);
+ DECLARE_OPCODE(disableScreenUpdate);
+ DECLARE_OPCODE(enableScreenUpdate);
+ DECLARE_OPCODE(incrementVariable);
+ DECLARE_OPCODE(changeStack);
+ DECLARE_OPCODE(disableMovie);
+ DECLARE_OPCODE(disableAllMovies);
+ DECLARE_OPCODE(enableMovie);
+ DECLARE_OPCODE(playMovie);
+ DECLARE_OPCODE(playMovieBg);
+ DECLARE_OPCODE(stopMovie);
+ DECLARE_OPCODE(unk_36);
+ DECLARE_OPCODE(fadeAmbientSounds);
+ DECLARE_OPCODE(complexPlayMovie);
+ DECLARE_OPCODE(activatePLST);
+ DECLARE_OPCODE(activateSLST);
+ DECLARE_OPCODE(activateMLSTAndPlay);
+ DECLARE_OPCODE(activateBLST);
+ DECLARE_OPCODE(activateFLST);
+ DECLARE_OPCODE(zipMode);
+ DECLARE_OPCODE(activateMLST);
+ DECLARE_OPCODE(activateSLSTWithVolume);
+};
+
+} // End of namespace Mohawk
+
+#undef DECLARE_OPCODE
+
+#endif
diff --git a/engines/mohawk/riven_vars.cpp b/engines/mohawk/riven_vars.cpp
new file mode 100644
index 0000000000..263ace2b78
--- /dev/null
+++ b/engines/mohawk/riven_vars.cpp
@@ -0,0 +1,324 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/str.h"
+
+#include "mohawk/riven.h"
+
+namespace Mohawk {
+
+// The Riven variable system is complex. The scripts of each stack give a number, but the number has to be matched
+// to a variable name defined in NAME resource 4.
+
+static const char *variableNames[] = {
+ // aspit
+ "aatrusbook",
+ "aatruspage",
+ "acathbook",
+ "acathpage",
+ "acathstate",
+ "adoit",
+ "adomecombo",
+ "agehn",
+ "ainventory",
+ "aova",
+ "apower",
+ "araw",
+ "atemp",
+ "atrap",
+ "atrapbook",
+ "auservolume",
+ "azip",
+
+ // bspit
+ "bbacklock",
+ "bbait",
+ "bbigbridge",
+ "bbirds",
+ "bblrarm",
+ "bblrdoor",
+ "bblrgrt",
+ "bblrsw",
+ "bblrvalve",
+ "bblrwtr",
+ "bbook",
+ "bbrlever",
+ "bcavedoor",
+ "bcombo",
+ "bcpipegr",
+ "bcratergg",
+ "bdome",
+ "bdrwr",
+ "bfans",
+ "bfmdoor",
+ "bidvlv",
+ "blab",
+ "blabbackdr",
+ "blabbook",
+ "blabeye",
+ "blabfrontdr",
+ "blabpage",
+ "blever",
+ "bfrontlock",
+ "bheat",
+ "bmagcar",
+ "bpipdr",
+ "bprs",
+ "bstove",
+ "btrap",
+ "bvalve",
+ "bvise",
+ "bytram",
+ "bytramtime",
+ "bytrap",
+ "bytrapped",
+
+ // gspit
+ "gbook",
+ "gcathtime",
+ "gcathstate",
+ "gcombo",
+ "gdome",
+ "gemagcar",
+ "gimagecurr",
+ "gimagemax",
+ "gimagerot",
+ "glkbtns",
+ "glkbridge",
+ "glkelev",
+ "glview",
+ "glviewmpos",
+ "glviewpos",
+ "gnmagrot",
+ "gnmagcar",
+ "gpinup",
+ "gpinpos",
+ "gpinsmpos",
+ "grview",
+ "grviewmpos",
+ "grviewpos",
+ "gscribe",
+ "gscribetime",
+ "gsubelev",
+ "gsubdr",
+ "gupmoov",
+ "gwhark",
+ "gwharktime",
+
+ // jspit
+ "jwmagcar",
+ "jbeetle",
+ "jbeetlepool",
+ "jbook",
+ "jbridge1",
+ "jbridge2",
+ "jbridge3",
+ "jbridge4",
+ "jbridge5",
+ "jccb",
+ "jcombo",
+ "jcrg",
+ "jdome",
+ "jdrain",
+ "jgallows",
+ "jgate",
+ "jgirl",
+ "jiconcorrectorder",
+ "jiconorder",
+ "jicons",
+ "jladder",
+ "jleftpos",
+ "jpeek",
+ "jplaybeetle",
+ "jprebel",
+ "jprisondr",
+ "jprisonsecdr",
+ "jrbook",
+ "jrightpos",
+ "jsouthpathdr",
+ "jschooldr",
+ "jsub",
+ "jsubdir",
+ "jsubhatch",
+ "jsubsw",
+ "jsunners",
+ "jsunnertime",
+ "jthronedr",
+ "jtunneldr",
+ "jtunnellamps",
+ "jvillagepeople",
+ "jwarning",
+ "jwharkpos",
+ "jwharkram",
+ "jwmouth",
+ "jwmagcar",
+ "jymagcar",
+
+ // ospit
+ "oambient",
+ "obutton",
+ "ocage",
+ "odeskbook",
+ "ogehnpage",
+ "omusicplayer",
+ "ostanddrawer",
+ "ostove",
+
+ // pspit
+ "pbook",
+ "pcage",
+ "pcathcheck",
+ "pcathstate",
+ "pcathtime",
+ "pcombo",
+ "pcorrectorder",
+ "pdome",
+ "pelevcombo",
+ "pleftpos",
+ "prightpos",
+ "ptemp",
+ "pwharkpos",
+
+ // rspit
+ "rrebel",
+ "rrebelview",
+ "rrichard",
+ "rvillagetime",
+
+ // tspit
+ "tbars",
+ "tbeetle",
+ "tblue",
+ "tbook",
+ "tbookvalve",
+ "tcage",
+ "tcombo",
+ "tcorrectorder",
+ "tcovercombo",
+ "tdl",
+ "tdome",
+ "tdomeelev",
+ "tdomeelevbtn",
+ "tgatebrhandle",
+ "tgatebridge",
+ "tgatestate",
+ "tgreen",
+ "tgridoor",
+ "tgrodoor",
+ "tgrmdoor",
+ "tguard",
+ "timagedoor",
+ "tmagcar",
+ "torange",
+ "tred",
+ "tsecdoor",
+ "tsubbridge",
+ "ttelecover",
+ "ttelehandle",
+ "ttelepin",
+ "ttelescope",
+ "ttelevalve",
+ "ttemple",
+ "ttempledoor",
+ "ttunneldoor",
+ "tviewer",
+ "tviolet",
+ "twabrvalve",
+ "twaffle",
+ "tyellow",
+
+ // Miscellaneous
+ "elevbtn1",
+ "elevbtn2",
+ "elevbtn3",
+ "domecheck",
+ "transitionsenabled",
+ "transitionmode",
+ "waterenabled",
+ "rivenambients",
+ "stackvarsinitialized",
+ "doingsetupscreens",
+ "all_book",
+ "playerhasbook",
+ "returnstackid",
+ "returncardid",
+ "newpos",
+ "themarble",
+ "currentstackid",
+ "currentcardid"
+};
+
+uint32 *MohawkEngine_Riven::getLocalVar(uint32 index) {
+ return matchVarToString(getName(VariableNames, index));
+}
+
+uint32 MohawkEngine_Riven::getGlobalVar(uint32 index) {
+ return _vars[index];
+}
+
+Common::String MohawkEngine_Riven::getGlobalVarName(uint32 index) {
+ return Common::String(variableNames[index]);
+}
+
+uint32 *MohawkEngine_Riven::matchVarToString(Common::String varName) {
+ return matchVarToString(varName.c_str());
+}
+
+uint32 *MohawkEngine_Riven::matchVarToString(const char *varName) {
+ for (uint32 i = 0; i < _varCount; i++)
+ if (!scumm_stricmp(varName, variableNames[i]))
+ return &_vars[i];
+ error ("Unknown variable: \'%s\'", varName);
+ return NULL;
+}
+
+void MohawkEngine_Riven::initVars() {
+ _varCount = ARRAYSIZE(variableNames);
+
+ _vars = new uint32[_varCount];
+
+ // Temporary:
+ for (uint32 i = 0; i < _varCount; i++)
+ _vars[i] = 0;
+
+ // Init Variables to their correct starting state.
+ *matchVarToString("ttelescope") = 5;
+ *matchVarToString("tgatestate") = 1;
+ *matchVarToString("jbridge1") = 1;
+ *matchVarToString("jbridge4") = 1;
+ *matchVarToString("jgallows") = 1;
+ *matchVarToString("jiconcorrectorder") = 12068577;
+ *matchVarToString("bblrvalve") = 1;
+ *matchVarToString("bblrwtr") = 1;
+ *matchVarToString("bfans") = 1;
+ *matchVarToString("bytrap") = 2;
+ *matchVarToString("aatruspage") = 1;
+ *matchVarToString("acathpage") = 1;
+ *matchVarToString("bheat") = 1;
+ *matchVarToString("waterenabled") = 1;
+ *matchVarToString("ogehnpage") = 1;
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/sound.cpp b/engines/mohawk/sound.cpp
new file mode 100644
index 0000000000..7426e07084
--- /dev/null
+++ b/engines/mohawk/sound.cpp
@@ -0,0 +1,530 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "mohawk/sound.h"
+
+#include "common/util.h"
+
+#include "sound/audiostream.h"
+#include "sound/mp3.h"
+#include "sound/wave.h"
+
+
+namespace Mohawk {
+
+Sound::Sound(MohawkEngine* vm) : _vm(vm) {
+ _rivenSoundFile = NULL;
+ _midiDriver = NULL;
+ _midiParser = NULL;
+
+ for (uint32 i = 0; i < _handles.size(); i++) {
+ _handles[i].handle = Audio::SoundHandle();
+ _handles[i].type = kFreeHandle;
+ }
+
+ initMidi();
+}
+
+Sound::~Sound() {
+ stopSound();
+ stopAllSLST();
+ delete _rivenSoundFile;
+
+ if (_midiDriver) {
+ _midiDriver->close();
+ delete _midiDriver;
+ }
+
+ if (_midiParser) {
+ _midiParser->unloadMusic();
+ delete _midiParser;
+ }
+}
+
+void Sound::loadRivenSounds(uint16 stack) {
+ static const char prefixes[] = { 'a', 'b', 'g', 'j', 'o', 'p', 'r', 't' };
+
+ if (!_rivenSoundFile)
+ _rivenSoundFile = new MohawkFile();
+
+ _rivenSoundFile->open(Common::String(prefixes[stack]) + "_Sounds.mhk");
+}
+
+void Sound::initMidi() {
+ if (!(_vm->getFeatures() & GF_HASMIDI))
+ return;
+
+ // Let's get our MIDI parser/driver
+ _midiParser = MidiParser::createParser_SMF();
+ _midiDriver = MidiDriver::createMidi(MidiDriver::detectMusicDriver(MDT_ADLIB|MDT_MIDI));
+
+ // Set up everything!
+ _midiDriver->open();
+ _midiParser->setMidiDriver(_midiDriver);
+ _midiParser->setTimerRate(_midiDriver->getBaseTempo());
+}
+
+Audio::SoundHandle *Sound::playSound(uint16 id, bool mainSoundFile, byte volume) {
+ debug (0, "Playing sound %d", id);
+
+ SndHandle *handle = getHandle();
+ handle->type = kUsedHandle;
+
+ Audio::AudioStream* audStream = NULL;
+
+ switch (_vm->getGameType()) {
+ case GType_MYST:
+ if (_vm->getFeatures() & GF_ME) {
+ // Myst ME is a bit more efficient with sound storage than Myst
+ // Myst has lots of sounds repeated. To overcome this, Myst ME
+ // has MJMP resources which provide a link to the actual MSND
+ // resource we're looking for. This saves a lot of space from
+ // repeated data.
+ if (_vm->hasResource(ID_MJMP, id)) {
+ Common::SeekableReadStream *mjmpStream = _vm->getRawData(ID_MJMP, id);
+ id = mjmpStream->readUint16LE();
+ delete mjmpStream;
+ }
+
+ audStream = Audio::makeWAVStream(_vm->getRawData(ID_MSND, id), true);
+ } else
+ audStream = makeMohawkWaveStream(_vm->getRawData(ID_MSND, id));
+ break;
+ case GType_RIVEN:
+ if (mainSoundFile)
+ audStream = makeMohawkWaveStream(_rivenSoundFile->getRawData(ID_TWAV, id));
+ else
+ audStream = makeMohawkWaveStream(_vm->getRawData(ID_TWAV, id));
+ break;
+ case GType_ZOOMBINI:
+ audStream = makeMohawkWaveStream(_vm->getRawData(ID_SND, id));
+ break;
+ case GType_CSAMTRAK:
+ if (mainSoundFile)
+ audStream = makeMohawkWaveStream(_vm->getRawData(ID_TWAV, id));
+ else
+ audStream = getCSAmtrakMusic(id);
+ break;
+ case GType_OLDLIVINGBOOKS:
+ audStream = makeOldMohawkWaveStream(_vm->getRawData(ID_WAV, id));
+ break;
+ default:
+ audStream = makeMohawkWaveStream(_vm->getRawData(ID_TWAV, id));
+ }
+
+ if (audStream) {
+ _vm->_mixer->playInputStream(Audio::Mixer::kPlainSoundType, &handle->handle, audStream, -1, volume);
+ return &handle->handle;
+ }
+
+ return NULL;
+}
+
+void Sound::playMidi(uint16 id) {
+ uint32 idTag;
+ if (!(_vm->getFeatures() & GF_HASMIDI)) {
+ warning ("Attempting to play MIDI in a game without MIDI");
+ return;
+ }
+
+ assert(_midiDriver && _midiParser);
+
+ _midiParser->unloadMusic();
+ Common::SeekableReadStream *midi = _vm->getRawData(ID_TMID, id);
+
+ idTag = midi->readUint32BE();
+ assert(idTag == ID_MHWK);
+ midi->readUint32BE(); // Skip size
+ idTag = midi->readUint32BE();
+ assert(idTag == ID_MIDI);
+
+ byte *midiData = (byte *)malloc(midi->size() - 12); // Enough to cover MThd/Prg#/MTrk
+
+ // Read the MThd Data
+ midi->read(midiData, 14);
+
+ // Skip the unknown Prg# section
+ idTag = midi->readUint32BE();
+ assert(idTag == ID_PRG);
+ midi->skip(midi->readUint32BE());
+
+ // Read the MTrk Data
+ uint32 mtrkSize = midi->size() - midi->pos();
+ midi->read(midiData + 14, mtrkSize);
+
+ delete midi;
+
+ // Now, play it :)
+ if (!_midiParser->loadMusic(midiData, 14 + mtrkSize))
+ error ("Could not play MIDI music from tMID %04x\n", id);
+
+ _midiDriver->setTimerCallback(_midiParser, MidiParser::timerCallback);
+}
+
+void Sound::playSLST(uint16 index, uint16 card) {
+ Common::SeekableReadStream *slstStream = _vm->getRawData(ID_SLST, card);
+ SLSTRecord slstRecord;
+ uint16 recordCount = slstStream->readUint16BE();
+
+ for (uint16 i = 0; i < recordCount; i++) {
+ slstRecord.index = slstStream->readUint16BE();
+ slstRecord.sound_count = slstStream->readUint16BE();
+ slstRecord.sound_ids = new uint16[slstRecord.sound_count];
+
+ for (uint16 j = 0; j < slstRecord.sound_count; j++)
+ slstRecord.sound_ids[j] = slstStream->readUint16BE();
+
+ slstRecord.fade_flags = slstStream->readUint16BE();
+ slstRecord.loop = slstStream->readUint16BE();
+ slstRecord.global_volume = slstStream->readUint16BE();
+ slstRecord.u0 = slstStream->readUint16BE(); // Unknown
+
+ if (slstRecord.u0 > 1)
+ warning("slstRecord.u0: %d non-boolean", slstRecord.u0);
+
+ slstRecord.u1 = slstStream->readUint16BE(); // Unknown
+
+ if (slstRecord.u1 != 0)
+ warning("slstRecord.u1: %d non-zero", slstRecord.u1);
+
+ slstRecord.volumes = new uint16[slstRecord.sound_count];
+ slstRecord.balances = new int16[slstRecord.sound_count];
+ slstRecord.u2 = new uint16[slstRecord.sound_count];
+
+ for (uint16 j = 0; j < slstRecord.sound_count; j++)
+ slstRecord.volumes[j] = slstStream->readUint16BE();
+
+ for (uint16 j = 0; j < slstRecord.sound_count; j++)
+ slstRecord.balances[j] = slstStream->readSint16BE(); // negative = left, 0 = center, positive = right
+
+ for (uint16 j = 0; j < slstRecord.sound_count; j++) {
+ slstRecord.u2[j] = slstStream->readUint16BE(); // Unknown
+
+ if (slstRecord.u2[j] != 255 && slstRecord.u2[j] != 256)
+ warning("slstRecord.u2[%d]: %d not 255 or 256", j, slstRecord.u2[j]);
+ }
+
+ if (slstRecord.index == index) {
+ playSLST(slstRecord);
+ delete slstStream;
+ return;
+ }
+
+ delete[] slstRecord.sound_ids;
+ delete[] slstRecord.volumes;
+ delete[] slstRecord.balances;
+ delete[] slstRecord.u2;
+ }
+
+ delete slstStream;
+ // No matching records, assume we need to stop all SLST's
+ stopAllSLST();
+}
+
+void Sound::playSLST(SLSTRecord slstRecord) {
+ // End old sounds
+ for (uint16 i = 0; i < _currentSLSTSounds.size(); i++) {
+ bool noLongerPlay = true;
+ for (uint16 j = 0; j < slstRecord.sound_count; j++)
+ if (_currentSLSTSounds[i].id == slstRecord.sound_ids[j])
+ noLongerPlay = false;
+ if (noLongerPlay)
+ stopSLSTSound(i, (slstRecord.fade_flags & 1) != 0);
+ }
+
+ // Start new sounds
+ for (uint16 i = 0; i < slstRecord.sound_count; i++) {
+ bool alreadyPlaying = false;
+ for (uint16 j = 0; j < _currentSLSTSounds.size(); j++) {
+ if (_currentSLSTSounds[j].id == slstRecord.sound_ids[i])
+ alreadyPlaying = true;
+ }
+ if (!alreadyPlaying) {
+ playSLSTSound(slstRecord.sound_ids[i],
+ (slstRecord.fade_flags & (1 << 1)) != 0,
+ slstRecord.loop != 0,
+ slstRecord.volumes[i],
+ slstRecord.balances[i]);
+ }
+ }
+}
+
+void Sound::stopAllSLST() {
+ for (uint16 i = 0; i < _currentSLSTSounds.size(); i++)
+ _vm->_mixer->stopHandle(*_currentSLSTSounds[i].handle);
+ _currentSLSTSounds.clear();
+}
+
+static int8 convertBalance(int16 balance) {
+ return (int8)(balance >> 8);
+}
+
+void Sound::playSLSTSound(uint16 id, bool fade, bool loop, uint16 volume, int16 balance) {
+ // WORKAROUND: Some Riven SLST entries have a volume of 0, so we just ignore them.
+ if (volume == 0)
+ return;
+
+ SLSTSndHandle sndHandle;
+ sndHandle.handle = new Audio::SoundHandle();
+ sndHandle.id = id;
+ _currentSLSTSounds.push_back(sndHandle);
+
+ Audio::AudioStream *audStream = makeMohawkWaveStream(_rivenSoundFile->getRawData(ID_TWAV, id), loop);
+
+ // The max mixer volume is 255 and the max Riven volume is 256. Just change it to 255.
+ if (volume == 256)
+ volume = 255;
+
+ // TODO: Handle fading, possibly just raise the volume of the channel in increments?
+
+ _vm->_mixer->playInputStream(Audio::Mixer::kPlainSoundType, sndHandle.handle, audStream, -1, volume, convertBalance(balance));
+}
+
+void Sound::stopSLSTSound(uint16 index, bool fade) {
+ // TODO: Fade out, mixer needs to be extended to get volume on a handle
+ _vm->_mixer->stopHandle(*_currentSLSTSounds[index].handle);
+ _currentSLSTSounds.remove_at(index);
+}
+
+void Sound::pauseSLST() {
+ for (uint16 i = 0; i < _currentSLSTSounds.size(); i++)
+ _vm->_mixer->pauseHandle(*_currentSLSTSounds[i].handle, true);
+}
+
+void Sound::resumeSLST() {
+ for (uint16 i = 0; i < _currentSLSTSounds.size(); i++)
+ _vm->_mixer->pauseHandle(*_currentSLSTSounds[i].handle, false);
+}
+
+Audio::AudioStream *Sound::getCSAmtrakMusic(uint16 id) {
+ char filename[18];
+ sprintf(filename, "MUSIC/MUSIC%02d.MHK", id);
+ MohawkFile *file = new MohawkFile();
+ file->open(filename);
+ Audio::AudioStream *audStream = makeMohawkWaveStream(file->getRawData(ID_TWAV, 2000 + id));
+ delete file;
+ return audStream;
+}
+
+Audio::AudioStream *Sound::makeMohawkWaveStream(Common::SeekableReadStream *stream, bool loop) {
+ bool foundData = false;
+ uint32 tag = 0;
+ ADPC_Chunk adpc;
+ Cue_Chunk cue;
+ Data_Chunk data_chunk;
+
+ if (stream->readUint32BE() == ID_MHWK) // MHWK tag again
+ debug(2, "Found Tag MHWK");
+ else
+ error ("Could not find tag \'MHWK\'");
+
+ stream->readUint32BE(); // Skip size
+
+ if (stream->readUint32BE() == ID_WAVE)
+ debug(2, "Found Tag WAVE");
+ else
+ error ("Could not find tag \'WAVE\'");
+
+ while (!foundData) {
+ tag = stream->readUint32BE();
+
+ switch (tag) {
+ case ID_ADPC:
+ debug(2, "Found Tag ADPC");
+ // Riven ADPCM Sound Only
+ // NOTE: This is completely useless for us. All of this
+ // is already in the ADPCM decoder in /sound.
+ adpc.size = stream->readUint32BE();
+ adpc.u0 = stream->readUint16BE();
+ adpc.channels = stream->readUint16BE();
+ adpc.u1 = stream->readUint32BE();
+
+ for (uint16 i = 0; i < adpc.channels; i++)
+ adpc.u2[i] = stream->readUint32BE();
+ if (adpc.u0 == 2) {
+ adpc.u3 = stream->readUint32BE();
+ for (uint16 i = 0; i < adpc.channels; i++)
+ adpc.u4[i] = stream->readUint32BE();
+ }
+ break;
+ case ID_CUE:
+ debug(2, "Found Tag Cue#");
+ // I have not tested this with Myst, but the one Riven test-case,
+ // pspit tWAV 3, has two cue points: "Beg Loop" and "End Loop".
+ // So, my guess is that a cue chunk just holds where to loop the
+ // sound. Some cue chunks even have no point count (such as
+ // Myst's intro.dat MSND 2. So, my theory is that a cue chunk
+ // always represents a loop, and if there is a point count, that
+ // represents the points from which to loop.
+ //
+ // This theory is probably not entirely true anymore. I've found
+ // that the values (which were previously unknown) in the DATA
+ // chunk are for looping. Since it was only used in Myst, it was
+ // always 10 0's, Tito just thought it was useless. I'm still not
+ // sure what purpose this has.
+
+ cue.size = stream->readUint32BE();
+ cue.point_count = stream->readUint16BE();
+
+ if (cue.point_count == 0)
+ debug (2, "Cue# chunk found with no points!");
+ else
+ debug (2, "Cue# chunk found with %d point(s)!", cue.point_count);
+
+ for (uint16 i = 0; i < cue.point_count; i++) {
+ cue.cueList[i].position = stream->readUint32BE();
+ cue.cueList[i].length = stream->readByte();
+ for (byte j = 0; j < cue.cueList[i].length; j++)
+ cue.cueList[i].name += stream->readByte();
+ // Realign to uint16 boundaries...
+ if (!(cue.cueList[i].length & 1))
+ stream->readByte();
+ debug (3, "Cue# chunk point %d: %s", i, cue.cueList[i].name.c_str());
+ }
+ break;
+ case ID_DATA:
+ debug(2, "Found Tag DATA");
+ // We subtract 20 from the actual chunk size, which is the total size
+ // of the chunk's header
+ data_chunk.size = stream->readUint32BE() - 20;
+ data_chunk.sample_rate = stream->readUint16BE();
+ data_chunk.sample_count = stream->readUint32BE();
+ data_chunk.bitsPerSample = stream->readByte();
+ data_chunk.channels = stream->readByte();
+ data_chunk.encoding = stream->readUint16BE();
+ data_chunk.loop = stream->readUint16BE();
+ data_chunk.loopStart = stream->readUint32BE();
+ data_chunk.loopEnd = stream->readUint32BE();
+
+ data_chunk.audio_data = (byte *)malloc(data_chunk.size);
+ stream->read(data_chunk.audio_data, data_chunk.size);
+ foundData = true;
+ break;
+ default:
+ error ("Unknown tag found in 'tWAV' chunk -- \'%s\'", tag2str(tag));
+ }
+ }
+
+ // makeMohawkWaveStream always takes control of the original stream
+ delete stream;
+
+ // The sound in Myst uses raw unsigned 8-bit data
+ // The sound in the CD version of Riven is encoded in Intel DVI ADPCM
+ // The sound in the DVD version of Riven is encoded in MPEG-2 Layer II or Intel DVI ADPCM
+ if (data_chunk.encoding == kCodecRaw) {
+ byte flags = Audio::Mixer::FLAG_UNSIGNED|Audio::Mixer::FLAG_AUTOFREE;
+ if (data_chunk.channels == 2)
+ flags |= Audio::Mixer::FLAG_STEREO;
+ if (data_chunk.loop == 0xFFFF || loop)
+ flags |= Audio::Mixer::FLAG_LOOP;
+ return Audio::makeLinearInputStream(data_chunk.audio_data, data_chunk.size, data_chunk.sample_rate, flags, data_chunk.loopStart, data_chunk.loopEnd);
+ } else if (data_chunk.encoding == kCodecADPCM) {
+ Common::MemoryReadStream *dataStream = new Common::MemoryReadStream(data_chunk.audio_data, data_chunk.size, Common::DisposeAfterUse::YES);
+ uint32 blockAlign = data_chunk.channels * data_chunk.bitsPerSample / 8;
+ return Audio::makeADPCMStream(dataStream, true, data_chunk.size, Audio::kADPCMIma, data_chunk.sample_rate, data_chunk.channels, blockAlign, !loop);
+ } else if (data_chunk.encoding == kCodecMPEG2) {
+#ifdef USE_MAD
+ Common::MemoryReadStream *dataStream = new Common::MemoryReadStream(data_chunk.audio_data, data_chunk.size, Common::DisposeAfterUse::YES);
+ return Audio::makeMP3Stream(dataStream, true, 0, 0, !loop);
+#else
+ warning ("MAD library not included - unable to play MP2 audio");
+#endif
+ } else {
+ error ("Unknown Mohawk WAVE encoding %d", data_chunk.encoding);
+ }
+
+ return NULL;
+}
+
+Audio::AudioStream *Sound::makeOldMohawkWaveStream(Common::SeekableReadStream *stream, bool loop) {
+ uint16 header = stream->readUint16BE();
+ uint16 rate = 0;
+ uint32 size = 0;
+
+ if (header == 'Wv') { // Big Endian
+ rate = stream->readUint16BE();
+ stream->skip(10); // Loop chunk, like the newer format?
+ size = stream->readUint32BE();
+ } else if (header == 'vW') { // Little Endian
+ rate = stream->readUint16LE();
+ stream->skip(10); // Loop chunk, like the newer format?
+ size = stream->readUint32LE();
+ } else
+ error("Could not find Old Mohawk Sound header");
+
+ assert(size);
+ byte *data = (byte *)malloc(size);
+ stream->read(data, size);
+ delete stream;
+
+ byte flags = Audio::Mixer::FLAG_UNSIGNED|Audio::Mixer::FLAG_AUTOFREE;
+
+ if (loop)
+ flags |= Audio::Mixer::FLAG_LOOP;
+
+ return Audio::makeLinearInputStream(data, size, rate, flags, 0, 0);
+}
+
+SndHandle *Sound::getHandle() {
+ for (uint32 i = 0; i < _handles.size(); i++) {
+ if (_handles[i].type == kFreeHandle)
+ return &_handles[i];
+
+ if (!_vm->_mixer->isSoundHandleActive(_handles[i].handle)) {
+ _handles[i].type = kFreeHandle;
+ return &_handles[i];
+ }
+ }
+
+ // Let's add a new sound handle!
+ SndHandle handle;
+ handle.handle = Audio::SoundHandle();
+ handle.type = kFreeHandle;
+ _handles.push_back(handle);
+
+ return &_handles[_handles.size() - 1];
+}
+
+void Sound::stopSound() {
+ for (uint32 i = 0; i < _handles.size(); i++)
+ if (_handles[i].type == kUsedHandle) {
+ _vm->_mixer->stopHandle(_handles[i].handle);
+ _handles[i].type = kFreeHandle;
+ }
+}
+
+void Sound::pauseSound() {
+ for (uint32 i = 0; i < _handles.size(); i++)
+ if (_handles[i].type == kUsedHandle)
+ _vm->_mixer->pauseHandle(_handles[i].handle, true);
+}
+
+void Sound::resumeSound() {
+ for (uint32 i = 0; i < _handles.size(); i++)
+ if (_handles[i].type == kUsedHandle)
+ _vm->_mixer->pauseHandle(_handles[i].handle, false);
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/sound.h b/engines/mohawk/sound.h
new file mode 100644
index 0000000000..44c4409b0c
--- /dev/null
+++ b/engines/mohawk/sound.h
@@ -0,0 +1,157 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MOHAWK_SOUND_H
+#define MOHAWK_SOUND_H
+
+#include "common/scummsys.h"
+#include "common/str.h"
+
+#include "sound/audiostream.h"
+#include "sound/adpcm.h"
+#include "sound/mididrv.h"
+#include "sound/midiparser.h"
+#include "sound/mixer.h"
+
+#include "mohawk/mohawk.h"
+#include "mohawk/file.h"
+
+namespace Mohawk {
+
+#define MAX_CHANNELS 2 // Can there be more than 2?
+#define CUE_MAX 256
+
+struct SLSTRecord {
+ uint16 index;
+ uint16 sound_count;
+ uint16* sound_ids;
+ uint16 fade_flags;
+ uint16 loop;
+ uint16 global_volume;
+ uint16 u0;
+ uint16 u1;
+ uint16* volumes;
+ int16* balances;
+ uint16* u2;
+};
+
+enum SndHandleType {
+ kFreeHandle,
+ kUsedHandle
+};
+
+struct SndHandle {
+ Audio::SoundHandle handle;
+ SndHandleType type;
+};
+
+struct SLSTSndHandle {
+ Audio::SoundHandle *handle;
+ uint16 id;
+};
+
+struct ADPC_Chunk { // Appears to only exist if there isn't MPEG-2 Audio
+ uint32 size;
+ uint16 u0; // Unknown (2 when there's a Cue# Chunk, 1 when there's not)
+ uint16 channels;
+ uint32 u1; // Unknown (always 0)
+ uint32 u2[MAX_CHANNELS]; // Unknown (0x00400000 for both channels)
+
+ // If there is a Cue# chunk, there can be two more variables:
+ uint32 u3;
+ uint32 u4[MAX_CHANNELS];
+};
+
+struct Cue_Chunk {
+ uint32 size;
+ uint16 point_count;
+ struct {
+ uint32 position;
+ byte length;
+ Common::String name;
+ } cueList[CUE_MAX];
+};
+
+enum {
+ kCodecRaw = 0,
+ kCodecADPCM = 1,
+ kCodecMPEG2 = 2
+};
+
+struct Data_Chunk {
+ uint32 size;
+ uint16 sample_rate;
+ uint32 sample_count;
+ byte bitsPerSample;
+ byte channels;
+ uint16 encoding;
+ uint16 loop;
+ uint32 loopStart;
+ uint32 loopEnd;
+ byte* audio_data;
+};
+
+class MohawkEngine;
+
+class Sound {
+public:
+ Sound(MohawkEngine*);
+ ~Sound();
+
+ void loadRivenSounds(uint16 stack);
+ Audio::SoundHandle *playSound(uint16 id, bool mainSoundFile = true, byte volume = Audio::Mixer::kMaxChannelVolume);
+ void playMidi(uint16 id);
+ void stopSound();
+ void pauseSound();
+ void resumeSound();
+ void playSLST(uint16 index, uint16 card);
+ void playSLST(SLSTRecord slstRecord);
+ void pauseSLST();
+ void resumeSLST();
+ void stopAllSLST();
+
+private:
+ MohawkEngine *_vm;
+ MohawkFile *_rivenSoundFile;
+ MidiDriver *_midiDriver;
+ MidiParser *_midiParser;
+
+ static Audio::AudioStream *getCSAmtrakMusic(uint16 id);
+ static Audio::AudioStream *makeMohawkWaveStream(Common::SeekableReadStream *stream, bool loop = false);
+ static Audio::AudioStream *makeOldMohawkWaveStream(Common::SeekableReadStream *stream, bool loop = false);
+ void initMidi();
+
+ Common::Array<SndHandle> _handles;
+ SndHandle *getHandle();
+
+ // Riven specific
+ void playSLSTSound(uint16 index, bool fade, bool loop, uint16 volume, int16 balance);
+ void stopSLSTSound(uint16 id, bool fade);
+ Common::Array<SLSTSndHandle> _currentSLSTSounds;
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/video/cinepak.cpp b/engines/mohawk/video/cinepak.cpp
new file mode 100644
index 0000000000..d5fa5fbcf6
--- /dev/null
+++ b/engines/mohawk/video/cinepak.cpp
@@ -0,0 +1,283 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "mohawk/video/cinepak.h"
+
+#include "common/system.h"
+#include "graphics/conversion.h" // For YUV2RGB
+
+// Code here partially based off of ffmpeg ;)
+
+namespace Mohawk {
+
+#define PUT_PIXEL(offset, lum, u, v) \
+ Graphics::YUV2RGB(lum, u, v, r, g, b); \
+ if (_pixelFormat.bytesPerPixel == 2) \
+ *((uint16 *)_curFrame.surface->pixels + offset) = _pixelFormat.RGBToColor(r, g, b); \
+ else \
+ *((uint32 *)_curFrame.surface->pixels + offset) = _pixelFormat.RGBToColor(r, g, b)
+
+CinepakDecoder::CinepakDecoder() : Graphics::Codec() {
+ _curFrame.surface = NULL;
+ _curFrame.strips = NULL;
+ _y = 0;
+ _pixelFormat = g_system->getScreenFormat();
+
+ // We're going to have to dither if we're running in 8bpp.
+ // We'll take RGBA8888 for best color performance in this case.
+ if (_pixelFormat.bytesPerPixel == 1)
+ _pixelFormat = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0);
+}
+
+CinepakDecoder::~CinepakDecoder() {
+ if (_curFrame.surface)
+ _curFrame.surface->free();
+ delete[] _curFrame.strips;
+}
+
+Graphics::Surface *CinepakDecoder::decodeImage(Common::SeekableReadStream *stream) {
+ _curFrame.flags = stream->readByte();
+ _curFrame.length = (stream->readByte() << 16) + stream->readUint16BE();
+ _curFrame.width = stream->readUint16BE();
+ _curFrame.height = stream->readUint16BE();
+ _curFrame.stripCount = stream->readUint16BE();
+
+ if (_curFrame.strips == NULL)
+ _curFrame.strips = new CinepakStrip[_curFrame.stripCount];
+
+ debug (4, "Cinepak Frame: Width = %d, Height = %d, Strip Count = %d", _curFrame.width, _curFrame.height, _curFrame.stripCount);
+
+#if 0
+ // Borrowed from FFMPEG. This should cut out the extra data Cinepak for Sega has (which is useless).
+ // The theory behind this is that this is here to confuse standard Cinepak decoders. But, we won't let that happen! ;)
+ if (_curFrame.length != (uint32)stream->size()) {
+ if (stream->readUint16BE() == 0xFE00)
+ stream->readUint32BE();
+ }
+#endif
+
+ if (!_curFrame.surface) {
+ _curFrame.surface = new Graphics::Surface();
+ _curFrame.surface->create(_curFrame.width, _curFrame.height, _pixelFormat.bytesPerPixel);
+ }
+
+ // Reset the y variable.
+ _y = 0;
+
+ for (uint16 i = 0; i < _curFrame.stripCount; i++) {
+ if (i > 0 && !(_curFrame.flags & 1)) { // Use codebooks from last strip
+ for (uint16 j = 0; j < 256; j++) {
+ _curFrame.strips[i].v1_codebook[j] = _curFrame.strips[i - 1].v1_codebook[j];
+ _curFrame.strips[i].v4_codebook[j] = _curFrame.strips[i - 1].v4_codebook[j];
+ }
+ }
+
+ _curFrame.strips[i].id = stream->readUint16BE();
+ _curFrame.strips[i].length = stream->readUint16BE() - 12; // Subtract the 12 byte header
+ _curFrame.strips[i].rect.top = _y; stream->readUint16BE(); // Ignore, substitute with our own.
+ _curFrame.strips[i].rect.left = 0; stream->readUint16BE(); // Ignore, substitute with our own
+ _curFrame.strips[i].rect.bottom = _y + stream->readUint16BE();
+ _curFrame.strips[i].rect.right = _curFrame.width; stream->readUint16BE(); // Ignore, substitute with our own
+
+ //printf ("Left = %d, Top = %d, Right = %d, Bottom = %d\n", _curFrame.strips[i].rect.left, _curFrame.strips[i].rect.top, _curFrame.strips[i].rect.right, _curFrame.strips[i].rect.bottom);
+
+ // Sanity check. Because Cinepak is based on 4x4 blocks, the width and height of each strip needs to be divisible by 4.
+ assert(!(_curFrame.strips[i].rect.width() % 4) && !(_curFrame.strips[i].rect.height() % 4));
+
+ uint32 pos = stream->pos();
+
+ while ((uint32)stream->pos() < (pos + _curFrame.strips[i].length) && !stream->eos()) {
+ byte chunkID = stream->readByte();
+
+ if (stream->eos())
+ break;
+
+ uint32 chunkSize = (stream->readByte() << 16) + stream->readUint16BE() - 4; // 24bit
+ int32 startPos = stream->pos();
+
+ switch (chunkID) {
+ case 0x20:
+ case 0x21:
+ case 0x24:
+ case 0x25:
+ loadCodebook(stream, i, 4, chunkID, chunkSize);
+ break;
+ case 0x22:
+ case 0x23:
+ case 0x26:
+ case 0x27:
+ loadCodebook(stream, i, 1, chunkID, chunkSize);
+ break;
+ case 0x30:
+ case 0x31:
+ case 0x32:
+ decodeVectors(stream, i, chunkID, chunkSize);
+ break;
+ default:
+ warning("Unknown Cinepak chunk ID %02x", chunkID);
+ return _curFrame.surface;
+ }
+
+ if (stream->pos() != startPos + (int32)chunkSize)
+ stream->seek(startPos + chunkSize);
+ }
+
+ _y = _curFrame.strips[i].rect.bottom;
+ }
+
+ return _curFrame.surface;
+}
+
+void CinepakDecoder::loadCodebook(Common::SeekableReadStream *stream, uint16 strip, byte codebookType, byte chunkID, uint32 chunkSize) {
+ CinepakCodebook *codebook = (codebookType == 1) ? _curFrame.strips[strip].v1_codebook : _curFrame.strips[strip].v4_codebook;
+
+ int32 startPos = stream->pos();
+ uint32 flag = 0, mask = 0;
+
+ for (uint16 i = 0; i < 256; i++) {
+ if ((chunkID & 0x01) && !(mask >>= 1)) {
+ if ((stream->pos() - startPos + 4) > (int32)chunkSize)
+ break;
+
+ flag = stream->readUint32BE();
+ mask = 0x80000000;
+ }
+
+ if (!(chunkID & 0x01) || (flag & mask)) {
+ byte n = (chunkID & 0x04) ? 4 : 6;
+ if ((stream->pos() - startPos + n) > (int32)chunkSize)
+ break;
+
+ for (byte j = 0; j < 4; j++)
+ codebook[i].y[j] = stream->readByte();
+
+ if (n == 6) {
+ codebook[i].u = stream->readByte() + 128;
+ codebook[i].v = stream->readByte() + 128;
+ } else {
+ /* this codebook type indicates either greyscale or
+ * palettized video; if palettized, U & V components will
+ * not be used so it is safe to set them to 128 for the
+ * benefit of greyscale rendering in YUV420P */
+ codebook[i].u = 128;
+ codebook[i].v = 128;
+ }
+ }
+ }
+}
+
+void CinepakDecoder::decodeVectors(Common::SeekableReadStream *stream, uint16 strip, byte chunkID, uint32 chunkSize) {
+ uint32 flag = 0, mask = 0;
+ uint32 iy[4];
+ int32 startPos = stream->pos();
+ byte r = 0, g = 0, b = 0;
+
+ for (uint16 y = _curFrame.strips[strip].rect.top; y < _curFrame.strips[strip].rect.bottom; y += 4) {
+ iy[0] = _curFrame.strips[strip].rect.left + y * _curFrame.width;
+ iy[1] = iy[0] + _curFrame.width;
+ iy[2] = iy[1] + _curFrame.width;
+ iy[3] = iy[2] + _curFrame.width;
+
+ for (uint16 x = _curFrame.strips[strip].rect.left; x < _curFrame.strips[strip].rect.right; x += 4) {
+ if ((chunkID & 0x01) && !(mask >>= 1)) {
+ if ((stream->pos() - startPos + 4) > (int32)chunkSize)
+ return;
+
+ flag = stream->readUint32BE();
+ mask = 0x80000000;
+ }
+
+ if (!(chunkID & 0x01) || (flag & mask)) {
+ if (!(chunkID & 0x02) && !(mask >>= 1)) {
+ if ((stream->pos() - startPos + 4) > (int32)chunkSize)
+ return;
+
+ flag = stream->readUint32BE();
+ mask = 0x80000000;
+ }
+
+ if ((chunkID & 0x02) || (~flag & mask)) {
+ if ((stream->pos() - startPos + 1) > (int32)chunkSize)
+ return;
+
+ // Get the codebook
+ CinepakCodebook codebook = _curFrame.strips[strip].v1_codebook[stream->readByte()];
+
+ PUT_PIXEL(iy[0] + 0, codebook.y[0], codebook.u, codebook.v);
+ PUT_PIXEL(iy[0] + 1, codebook.y[0], codebook.u, codebook.v);
+ PUT_PIXEL(iy[1] + 0, codebook.y[0], codebook.u, codebook.v);
+ PUT_PIXEL(iy[1] + 1, codebook.y[0], codebook.u, codebook.v);
+
+ PUT_PIXEL(iy[0] + 2, codebook.y[1], codebook.u, codebook.v);
+ PUT_PIXEL(iy[0] + 3, codebook.y[1], codebook.u, codebook.v);
+ PUT_PIXEL(iy[1] + 2, codebook.y[1], codebook.u, codebook.v);
+ PUT_PIXEL(iy[1] + 3, codebook.y[1], codebook.u, codebook.v);
+
+ PUT_PIXEL(iy[2] + 0, codebook.y[2], codebook.u, codebook.v);
+ PUT_PIXEL(iy[2] + 1, codebook.y[2], codebook.u, codebook.v);
+ PUT_PIXEL(iy[3] + 0, codebook.y[2], codebook.u, codebook.v);
+ PUT_PIXEL(iy[3] + 1, codebook.y[2], codebook.u, codebook.v);
+
+ PUT_PIXEL(iy[2] + 2, codebook.y[3], codebook.u, codebook.v);
+ PUT_PIXEL(iy[2] + 3, codebook.y[3], codebook.u, codebook.v);
+ PUT_PIXEL(iy[3] + 2, codebook.y[3], codebook.u, codebook.v);
+ PUT_PIXEL(iy[3] + 3, codebook.y[3], codebook.u, codebook.v);
+ } else if (flag & mask) {
+ if ((stream->pos() - startPos + 4) > (int32)chunkSize)
+ return;
+
+ CinepakCodebook codebook = _curFrame.strips[strip].v4_codebook[stream->readByte()];
+ PUT_PIXEL(iy[0] + 0, codebook.y[0], codebook.u, codebook.v);
+ PUT_PIXEL(iy[0] + 1, codebook.y[1], codebook.u, codebook.v);
+ PUT_PIXEL(iy[1] + 0, codebook.y[2], codebook.u, codebook.v);
+ PUT_PIXEL(iy[1] + 1, codebook.y[3], codebook.u, codebook.v);
+
+ codebook = _curFrame.strips[strip].v4_codebook[stream->readByte()];
+ PUT_PIXEL(iy[0] + 2, codebook.y[0], codebook.u, codebook.v);
+ PUT_PIXEL(iy[0] + 3, codebook.y[1], codebook.u, codebook.v);
+ PUT_PIXEL(iy[1] + 2, codebook.y[2], codebook.u, codebook.v);
+ PUT_PIXEL(iy[1] + 3, codebook.y[3], codebook.u, codebook.v);
+
+ codebook = _curFrame.strips[strip].v4_codebook[stream->readByte()];
+ PUT_PIXEL(iy[2] + 0, codebook.y[0], codebook.u, codebook.v);
+ PUT_PIXEL(iy[2] + 1, codebook.y[1], codebook.u, codebook.v);
+ PUT_PIXEL(iy[3] + 0, codebook.y[2], codebook.u, codebook.v);
+ PUT_PIXEL(iy[3] + 1, codebook.y[3], codebook.u, codebook.v);
+
+ codebook = _curFrame.strips[strip].v4_codebook[stream->readByte()];
+ PUT_PIXEL(iy[2] + 2, codebook.y[0], codebook.u, codebook.v);
+ PUT_PIXEL(iy[2] + 3, codebook.y[1], codebook.u, codebook.v);
+ PUT_PIXEL(iy[3] + 2, codebook.y[2], codebook.u, codebook.v);
+ PUT_PIXEL(iy[3] + 3, codebook.y[3], codebook.u, codebook.v);
+ }
+ }
+
+ for (byte i = 0; i < 4; i++)
+ iy[i] += 4;
+ }
+ }
+}
+
+}
diff --git a/engines/mohawk/video/cinepak.h b/engines/mohawk/video/cinepak.h
new file mode 100644
index 0000000000..a94d879bdb
--- /dev/null
+++ b/engines/mohawk/video/cinepak.h
@@ -0,0 +1,80 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef CINEPAK_H
+#define CINEPAK_H
+
+#include "common/scummsys.h"
+#include "common/stream.h"
+#include "common/rect.h"
+#include "graphics/surface.h"
+#include "graphics/pixelformat.h"
+
+#include "graphics/video/codecs/codec.h"
+
+namespace Mohawk {
+
+struct CinepakCodebook {
+ byte y[4];
+ byte u, v;
+};
+
+struct CinepakStrip {
+ uint16 id;
+ uint16 length;
+ Common::Rect rect;
+ CinepakCodebook v1_codebook[256], v4_codebook[256];
+};
+
+struct CinepakFrame {
+ byte flags;
+ uint32 length;
+ uint16 width;
+ uint16 height;
+ uint16 stripCount;
+ CinepakStrip *strips;
+
+ Graphics::Surface *surface;
+};
+
+class CinepakDecoder : public Graphics::Codec {
+public:
+ CinepakDecoder();
+ ~CinepakDecoder();
+
+ Graphics::Surface *decodeImage(Common::SeekableReadStream *stream);
+
+private:
+ CinepakFrame _curFrame;
+ int32 _y;
+ Graphics::PixelFormat _pixelFormat;
+
+ void loadCodebook(Common::SeekableReadStream *stream, uint16 strip, byte codebookType, byte chunkID, uint32 chunkSize);
+ void decodeVectors(Common::SeekableReadStream *stream, uint16 strip, byte chunkID, uint32 chunkSize);
+};
+
+}
+
+#endif
diff --git a/engines/mohawk/video/qdm2.cpp b/engines/mohawk/video/qdm2.cpp
new file mode 100644
index 0000000000..23a09f05d1
--- /dev/null
+++ b/engines/mohawk/video/qdm2.cpp
@@ -0,0 +1,3064 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+// Based off ffmpeg's QDM2 decoder
+
+#include "mohawk/video/qdm2.h"
+#include "mohawk/video/qdm2data.h"
+
+#include "common/system.h"
+
+namespace Mohawk {
+
+// Fix compilation for non C99-compliant compilers, like MSVC
+#ifndef int64_t
+typedef signed long long int int64_t;
+#endif
+
+// Integer log2 function. This is much faster than invoking
+// double precision C99 log2 math functions or equivalent, since
+// this is only used to determine maximum number of bits needed
+// i.e. only non-fractional part is needed. Also, the double
+// version is incorrect for exact cases due to floating point
+// rounding errors.
+static inline int scummvm_log2(int n) {
+ int ret = -1;
+ while(n != 0) {
+ n /= 2;
+ ret++;
+ }
+ return ret;
+}
+
+#define QDM2_LIST_ADD(list, size, packet) \
+ do { \
+ if (size > 0) \
+ list[size - 1].next = &list[size]; \
+ list[size].packet = packet; \
+ list[size].next = NULL; \
+ size++; \
+ } while(0)
+
+// Result is 8, 16 or 30
+#define QDM2_SB_USED(subSampling) (((subSampling) >= 2) ? 30 : 8 << (subSampling))
+
+#define FIX_NOISE_IDX(noiseIdx) \
+ if ((noiseIdx) >= 3840) \
+ (noiseIdx) -= 3840 \
+
+#define SB_DITHERING_NOISE(sb, noiseIdx) (_noiseTable[(noiseIdx)++] * sb_noise_attenuation[(sb)])
+
+static inline void initGetBits(GetBitContext *s, const uint8 *buffer, int bitSize) {
+ int bufferSize = (bitSize + 7) >> 3;
+
+ debug(1, "void initGetBits(GetBitContext *s, const uint8 *buffer, int bitSize)");
+
+ if (bufferSize < 0 || bitSize < 0) {
+ bufferSize = bitSize = 0;
+ buffer = NULL;
+ }
+
+ s->buffer = buffer;
+ s->sizeInBits = bitSize;
+ s->bufferEnd = buffer + bufferSize;
+ s->index = 0;
+}
+
+static inline int getBitsCount(GetBitContext *s) {
+ debug(1, "int getBitsCount(GetBitContext *s)");
+ return s->index;
+}
+
+static inline unsigned int getBits1(GetBitContext *s) {
+ int index;
+ uint8 result;
+
+ debug(1, "unsigned int getBits1(GetBitContext *s)");
+
+ index = s->index;
+ result = s->buffer[index >> 3];
+
+ debug(1, "index : %d", index);
+
+ result >>= (index & 0x07);
+ result &= 1;
+ index++;
+ s->index = index;
+
+ return result;
+}
+
+static inline unsigned int getBits(GetBitContext *s, int n) {
+ int tmp, reCache, reIndex;
+
+ debug(1, "unsigned int getBits(GetBitContext *s, int n)");
+
+ reIndex = s->index;
+
+ debug(1, "reIndex : %d", reIndex);
+
+ reCache = READ_LE_UINT32((const uint8 *)s->buffer + (reIndex >> 3)) >> (reIndex & 0x07);
+
+ tmp = (reCache) & ((uint32)0xffffffff >> (32 - n));
+
+ s->index = reIndex + n;
+
+ return tmp;
+}
+
+static inline void skipBits(GetBitContext *s, int n) {
+ int reIndex, reCache;
+
+ debug(1, "void skipBits(GetBitContext *s, int n)");
+
+ reIndex = s->index;
+ reCache = 0;
+
+ debug(1, "reIndex : %d", reIndex);
+
+ reCache = READ_LE_UINT32((const uint8 *)s->buffer + (reIndex >> 3)) >> (reIndex & 0x07);
+ s->index = reIndex + n;
+}
+
+#define BITS_LEFT(length, gb) ((length) - getBitsCount((gb)))
+
+static int splitRadixPermutation(int i, int n, int inverse) {
+ if (n <= 2)
+ return i & 1;
+
+ int m = n >> 1;
+
+ if(!(i & m))
+ return splitRadixPermutation(i, m, inverse) * 2;
+
+ m >>= 1;
+
+ if (inverse == !(i & m))
+ return splitRadixPermutation(i, m, inverse) * 4 + 1;
+
+ return splitRadixPermutation(i, m, inverse) * 4 - 1;
+}
+
+// sin(2*pi*x/n) for 0<=x<n/4, followed by n/2<=x<3n/4
+float ff_sin_16[8];
+float ff_sin_32[16];
+float ff_sin_64[32];
+float ff_sin_128[64];
+float ff_sin_256[128];
+float ff_sin_512[256];
+float ff_sin_1024[512];
+float ff_sin_2048[1024];
+float ff_sin_4096[2048];
+float ff_sin_8192[4096];
+float ff_sin_16384[8192];
+float ff_sin_32768[16384];
+float ff_sin_65536[32768];
+
+float *ff_sin_tabs[] = {
+ NULL, NULL, NULL, NULL,
+ ff_sin_16, ff_sin_32, ff_sin_64, ff_sin_128, ff_sin_256, ff_sin_512, ff_sin_1024,
+ ff_sin_2048, ff_sin_4096, ff_sin_8192, ff_sin_16384, ff_sin_32768, ff_sin_65536,
+};
+
+// cos(2*pi*x/n) for 0<=x<=n/4, followed by its reverse
+float ff_cos_16[8];
+float ff_cos_32[16];
+float ff_cos_64[32];
+float ff_cos_128[64];
+float ff_cos_256[128];
+float ff_cos_512[256];
+float ff_cos_1024[512];
+float ff_cos_2048[1024];
+float ff_cos_4096[2048];
+float ff_cos_8192[4096];
+float ff_cos_16384[8192];
+float ff_cos_32768[16384];
+float ff_cos_65536[32768];
+
+float *ff_cos_tabs[] = {
+ NULL, NULL, NULL, NULL,
+ ff_cos_16, ff_cos_32, ff_cos_64, ff_cos_128, ff_cos_256, ff_cos_512, ff_cos_1024,
+ ff_cos_2048, ff_cos_4096, ff_cos_8192, ff_cos_16384, ff_cos_32768, ff_cos_65536,
+};
+
+void initCosineTables(int index) {
+ int m = 1 << index;
+ double freq = 2 * PI / m;
+ float *tab = ff_cos_tabs[index];
+
+ for (int i = 0; i <= m / 4; i++)
+ tab[i] = cos(i * freq);
+
+ for (int i = 1; i < m / 4; i++)
+ tab[m / 2 - i] = tab[i];
+}
+
+void fftPermute(FFTContext *s, FFTComplex *z) {
+ const uint16 *revtab = s->revtab;
+ int np = 1 << s->nbits;
+
+ if (s->tmpBuf) {
+ // TODO: handle split-radix permute in a more optimal way, probably in-place
+ for (int j = 0; j < np; j++)
+ s->tmpBuf[revtab[j]] = z[j];
+ memcpy(z, s->tmpBuf, np * sizeof(FFTComplex));
+ return;
+ }
+
+ // reverse
+ for (int j = 0; j < np; j++) {
+ int k = revtab[j];
+ if (k < j) {
+ FFTComplex tmp = z[k];
+ z[k] = z[j];
+ z[j] = tmp;
+ }
+ }
+}
+
+#define DECL_FFT(n,n2,n4) \
+static void fft##n(FFTComplex *z) { \
+ fft##n2(z); \
+ fft##n4(z + n4 * 2); \
+ fft##n4(z + n4 * 3); \
+ pass(z, ff_cos_##n, n4 / 2); \
+}
+
+#ifndef M_SQRT1_2
+#define M_SQRT1_2 7.0710678118654752440E-1
+#endif
+
+#define sqrthalf (float)M_SQRT1_2
+
+#define BF(x,y,a,b) { \
+ x = a - b; \
+ y = a + b; \
+}
+
+#define BUTTERFLIES(a0, a1, a2, a3) { \
+ BF(t3, t5, t5, t1); \
+ BF(a2.re, a0.re, a0.re, t5); \
+ BF(a3.im, a1.im, a1.im, t3); \
+ BF(t4, t6, t2, t6); \
+ BF(a3.re, a1.re, a1.re, t4); \
+ BF(a2.im, a0.im, a0.im, t6); \
+}
+
+// force loading all the inputs before storing any.
+// this is slightly slower for small data, but avoids store->load aliasing
+// for addresses separated by large powers of 2.
+#define BUTTERFLIES_BIG(a0, a1, a2, a3) { \
+ float r0 = a0.re, i0 = a0.im, r1 = a1.re, i1 = a1.im; \
+ BF(t3, t5, t5, t1); \
+ BF(a2.re, a0.re, r0, t5); \
+ BF(a3.im, a1.im, i1, t3); \
+ BF(t4, t6, t2, t6); \
+ BF(a3.re, a1.re, r1, t4); \
+ BF(a2.im, a0.im, i0, t6); \
+}
+
+#define TRANSFORM(a0, a1, a2, a3, wre, wim) { \
+ t1 = a2.re * wre + a2.im * wim; \
+ t2 = a2.im * wre - a2.re * wim; \
+ t5 = a3.re * wre - a3.im * wim; \
+ t6 = a3.im * wre + a3.re * wim; \
+ BUTTERFLIES(a0, a1, a2, a3) \
+}
+
+#define TRANSFORM_ZERO(a0, a1, a2, a3) { \
+ t1 = a2.re; \
+ t2 = a2.im; \
+ t5 = a3.re; \
+ t6 = a3.im; \
+ BUTTERFLIES(a0, a1, a2, a3) \
+}
+
+// z[0...8n-1], w[1...2n-1]
+#define PASS(name) \
+static void name(FFTComplex *z, const float *wre, unsigned int n) { \
+ float t1, t2, t3, t4, t5, t6; \
+ int o1 = 2 * n; \
+ int o2 = 4 * n; \
+ int o3 = 6 * n; \
+ const float *wim = wre + o1; \
+ n--; \
+ \
+ TRANSFORM_ZERO(z[0], z[o1], z[o2], z[o3]); \
+ TRANSFORM(z[1], z[o1 + 1], z[o2 + 1], z[o3 + 1], wre[1], wim[-1]); \
+ \
+ do { \
+ z += 2; \
+ wre += 2; \
+ wim -= 2; \
+ TRANSFORM(z[0], z[o1], z[o2], z[o3], wre[0], wim[0]); \
+ TRANSFORM(z[1], z[o1 + 1],z[o2 + 1], z[o3 + 1], wre[1], wim[-1]); \
+ } while(--n); \
+}
+
+PASS(pass)
+#undef BUTTERFLIES
+#define BUTTERFLIES BUTTERFLIES_BIG
+PASS(pass_big)
+
+static void fft4(FFTComplex *z) {
+ float t1, t2, t3, t4, t5, t6, t7, t8;
+
+ BF(t3, t1, z[0].re, z[1].re);
+ BF(t8, t6, z[3].re, z[2].re);
+ BF(z[2].re, z[0].re, t1, t6);
+ BF(t4, t2, z[0].im, z[1].im);
+ BF(t7, t5, z[2].im, z[3].im);
+ BF(z[3].im, z[1].im, t4, t8);
+ BF(z[3].re, z[1].re, t3, t7);
+ BF(z[2].im, z[0].im, t2, t5);
+}
+
+static void fft8(FFTComplex *z) {
+ float t1, t2, t3, t4, t5, t6, t7, t8;
+
+ fft4(z);
+
+ BF(t1, z[5].re, z[4].re, -z[5].re);
+ BF(t2, z[5].im, z[4].im, -z[5].im);
+ BF(t3, z[7].re, z[6].re, -z[7].re);
+ BF(t4, z[7].im, z[6].im, -z[7].im);
+ BF(t8, t1, t3, t1);
+ BF(t7, t2, t2, t4);
+ BF(z[4].re, z[0].re, z[0].re, t1);
+ BF(z[4].im, z[0].im, z[0].im, t2);
+ BF(z[6].re, z[2].re, z[2].re, t7);
+ BF(z[6].im, z[2].im, z[2].im, t8);
+
+ TRANSFORM(z[1], z[3], z[5], z[7], sqrthalf, sqrthalf);
+}
+
+#undef BF
+
+DECL_FFT(16,8,4)
+DECL_FFT(32,16,8)
+DECL_FFT(64,32,16)
+DECL_FFT(128,64,32)
+DECL_FFT(256,128,64)
+DECL_FFT(512,256,128)
+#define pass pass_big
+DECL_FFT(1024,512,256)
+DECL_FFT(2048,1024,512)
+DECL_FFT(4096,2048,1024)
+DECL_FFT(8192,4096,2048)
+DECL_FFT(16384,8192,4096)
+DECL_FFT(32768,16384,8192)
+DECL_FFT(65536,32768,16384)
+
+void fftCalc(FFTContext *s, FFTComplex *z) {
+ static void (* const fftDispatch[])(FFTComplex*) = {
+ fft4, fft8, fft16, fft32, fft64, fft128, fft256, fft512, fft1024,
+ fft2048, fft4096, fft8192, fft16384, fft32768, fft65536,
+ };
+
+ fftDispatch[s->nbits - 2](z);
+}
+
+// complex multiplication: p = a * b
+#define CMUL(pre, pim, are, aim, bre, bim) \
+{\
+ float _are = (are); \
+ float _aim = (aim); \
+ float _bre = (bre); \
+ float _bim = (bim); \
+ (pre) = _are * _bre - _aim * _bim; \
+ (pim) = _are * _bim + _aim * _bre; \
+}
+
+/**
+ * Compute the middle half of the inverse MDCT of size N = 2^nbits,
+ * thus excluding the parts that can be derived by symmetry
+ * @param output N/2 samples
+ * @param input N/2 samples
+ */
+void imdctHalfC(FFTContext *s, float *output, const float *input) {
+ const uint16 *revtab = s->revtab;
+ const float *tcos = s->tcos;
+ const float *tsin = s->tsin;
+ FFTComplex *z = (FFTComplex *)output;
+
+ int n = 1 << s->mdctBits;
+ int n2 = n >> 1;
+ int n4 = n >> 2;
+ int n8 = n >> 3;
+
+ // pre rotation
+ const float *in1 = input;
+ const float *in2 = input + n2 - 1;
+ for (int k = 0; k < n4; k++) {
+ int j = revtab[k];
+ CMUL(z[j].re, z[j].im, *in2, *in1, tcos[k], tsin[k]);
+ in1 += 2;
+ in2 -= 2;
+ }
+
+ fftCalc(s, z);
+
+ // post rotation + reordering
+ for (int k = 0; k < n8; k++) {
+ float r0, i0, r1, i1;
+ CMUL(r0, i1, z[n8 - k - 1].im, z[n8 - k - 1].re, tsin[n8 - k - 1], tcos[n8 - k - 1]);
+ CMUL(r1, i0, z[n8 + k].im, z[n8 + k].re, tsin[n8 + k], tcos[n8 + k]);
+ z[n8 - k - 1].re = r0;
+ z[n8 - k - 1].im = i0;
+ z[n8 + k].re = r1;
+ z[n8 + k].im = i1;
+ }
+}
+
+/**
+ * Compute inverse MDCT of size N = 2^nbits
+ * @param output N samples
+ * @param input N/2 samples
+ */
+void imdctCalcC(FFTContext *s, float *output, const float *input) {
+ int n = 1 << s->mdctBits;
+ int n2 = n >> 1;
+ int n4 = n >> 2;
+
+ imdctHalfC(s, output + n4, input);
+
+ for (int k = 0; k < n4; k++) {
+ output[k] = -output[n2 - k - 1];
+ output[n - k - 1] = output[n2 + k];
+ }
+}
+
+/**
+ * Compute MDCT of size N = 2^nbits
+ * @param input N samples
+ * @param out N/2 samples
+ */
+void mdctCalcC(FFTContext *s, float *out, const float *input) {
+ const uint16 *revtab = s->revtab;
+ const float *tcos = s->tcos;
+ const float *tsin = s->tsin;
+ FFTComplex *x = (FFTComplex *)out;
+
+ int n = 1 << s->mdctBits;
+ int n2 = n >> 1;
+ int n4 = n >> 2;
+ int n8 = n >> 3;
+ int n3 = 3 * n4;
+
+ // pre rotation
+ for (int i = 0; i < n8; i++) {
+ float re = -input[2 * i + 3 * n4] - input[n3 - 1 - 2 * i];
+ float im = -input[n4 + 2 * i] + input[n4 - 1 - 2 * i];
+ int j = revtab[i];
+ CMUL(x[j].re, x[j].im, re, im, -tcos[i], tsin[i]);
+
+ re = input[2 * i] - input[n2 - 1 - 2 * i];
+ im = -(input[n2 + 2 * i] + input[n - 1 - 2 * i]);
+ j = revtab[n8 + i];
+ CMUL(x[j].re, x[j].im, re, im, -tcos[n8 + i], tsin[n8 + i]);
+ }
+
+ fftCalc(s, x);
+
+ // post rotation
+ for (int i = 0; i < n8; i++) {
+ float r0, i0, r1, i1;
+ CMUL(i1, r0, x[n8 - i - 1].re, x[n8 - i - 1].im, -tsin[n8 - i - 1], -tcos[n8 - i - 1]);
+ CMUL(i0, r1, x[n8 + i].re, x[n8 + i].im, -tsin[n8 + i], -tcos[n8 + i]);
+ x[n8 - i - 1].re = r0;
+ x[n8 - i - 1].im = i0;
+ x[n8 + i].re = r1;
+ x[n8 + i].im = i1;
+ }
+}
+
+int fftInit(FFTContext *s, int nbits, int inverse) {
+ int i, j, m, n;
+ float alpha, c1, s1, s2;
+
+ if (nbits < 2 || nbits > 16)
+ goto fail;
+
+ s->nbits = nbits;
+ n = 1 << nbits;
+ s->tmpBuf = NULL;
+
+ s->exptab = (FFTComplex *)malloc((n / 2) * sizeof(FFTComplex));
+ if (!s->exptab)
+ goto fail;
+
+ s->revtab = (uint16 *)malloc(n * sizeof(uint16));
+ if (!s->revtab)
+ goto fail;
+ s->inverse = inverse;
+
+ s2 = inverse ? 1.0 : -1.0;
+
+ s->fftPermute = fftPermute;
+ s->fftCalc = fftCalc;
+ s->imdctCalc = imdctCalcC;
+ s->imdctHalf = imdctHalfC;
+ s->mdctCalc = mdctCalcC;
+ s->splitRadix = 1;
+
+ if (s->splitRadix) {
+ for (j = 4; j <= nbits; j++)
+ initCosineTables(j);
+
+ for (i = 0; i < n; i++)
+ s->revtab[-splitRadixPermutation(i, n, s->inverse) & (n - 1)] = i;
+
+ s->tmpBuf = (FFTComplex *)malloc(n * sizeof(FFTComplex));
+ } else {
+ for (i = 0; i < n / 2; i++) {
+ alpha = 2 * PI * (float)i / (float)n;
+ c1 = cos(alpha);
+ s1 = sin(alpha) * s2;
+ s->exptab[i].re = c1;
+ s->exptab[i].im = s1;
+ }
+
+ //int np = 1 << nbits;
+ //int nblocks = np >> 3;
+ //int np2 = np >> 1;
+
+ // compute bit reverse table
+ for (i = 0; i < n; i++) {
+ m = 0;
+
+ for (j = 0; j < nbits; j++)
+ m |= ((i >> j) & 1) << (nbits - j - 1);
+
+ s->revtab[i] = m;
+ }
+ }
+
+ return 0;
+
+ fail:
+ free(&s->revtab);
+ free(&s->exptab);
+ free(&s->tmpBuf);
+ return -1;
+}
+
+/**
+ * Sets up a real FFT.
+ * @param nbits log2 of the length of the input array
+ * @param trans the type of transform
+ */
+int rdftInit(RDFTContext *s, int nbits, RDFTransformType trans) {
+ int n = 1 << nbits;
+ const double theta = (trans == RDFT || trans == IRIDFT ? -1 : 1) * 2 * PI / n;
+
+ s->nbits = nbits;
+ s->inverse = trans == IRDFT || trans == IRIDFT;
+ s->signConvention = trans == RIDFT || trans == IRIDFT ? 1 : -1;
+
+ if (nbits < 4 || nbits > 16)
+ return -1;
+
+ if (fftInit(&s->fft, nbits - 1, trans == IRDFT || trans == RIDFT) < 0)
+ return -1;
+
+ initCosineTables(nbits);
+ s->tcos = ff_cos_tabs[nbits];
+ s->tsin = ff_sin_tabs[nbits] + (trans == RDFT || trans == IRIDFT) * (n >> 2);
+
+ for (int i = 0; i < n >> 2; i++)
+ s->tsin[i] = sin(i*theta);
+
+ return 0;
+}
+
+/** Map one real FFT into two parallel real even and odd FFTs. Then interleave
+ * the two real FFTs into one complex FFT. Unmangle the results.
+ * ref: http://www.engineeringproductivitytools.com/stuff/T0001/PT10.HTM
+ */
+void rdftCalc(RDFTContext *s, float *data) {
+ FFTComplex ev, od;
+
+ const int n = 1 << s->nbits;
+ const float k1 = 0.5;
+ const float k2 = 0.5 - s->inverse;
+ const float *tcos = s->tcos;
+ const float *tsin = s->tsin;
+
+ if (!s->inverse) {
+ fftPermute(&s->fft, (FFTComplex *)data);
+ fftCalc(&s->fft, (FFTComplex *)data);
+ }
+
+ // i=0 is a special case because of packing, the DC term is real, so we
+ // are going to throw the N/2 term (also real) in with it.
+ ev.re = data[0];
+ data[0] = ev.re + data[1];
+ data[1] = ev.re - data[1];
+
+ int i;
+
+ for (i = 1; i < n >> 2; i++) {
+ int i1 = i * 2;
+ int i2 = n - i1;
+
+ // Separate even and odd FFTs
+ ev.re = k1 * (data[i1] + data[i2]);
+ od.im = -k2 * (data[i1] - data[i2]);
+ ev.im = k1 * (data[i1 + 1] - data[i2 + 1]);
+ od.re = k2 * (data[i1 + 1] + data[i2 + 1]);
+
+ // Apply twiddle factors to the odd FFT and add to the even FFT
+ data[i1] = ev.re + od.re * tcos[i] - od.im * tsin[i];
+ data[i1 + 1] = ev.im + od.im * tcos[i] + od.re * tsin[i];
+ data[i2] = ev.re - od.re * tcos[i] + od.im * tsin[i];
+ data[i2 + 1] = -ev.im + od.im * tcos[i] + od.re * tsin[i];
+ }
+
+ data[i * 2 + 1] = s->signConvention * data[i * 2 + 1];
+ if (s->inverse) {
+ data[0] *= k1;
+ data[1] *= k1;
+ fftPermute(&s->fft, (FFTComplex*)data);
+ fftCalc(&s->fft, (FFTComplex*)data);
+ }
+}
+
+// half mpeg encoding window (full precision)
+const int32 ff_mpa_enwindow[257] = {
+ 0, -1, -1, -1, -1, -1, -1, -2,
+ -2, -2, -2, -3, -3, -4, -4, -5,
+ -5, -6, -7, -7, -8, -9, -10, -11,
+ -13, -14, -16, -17, -19, -21, -24, -26,
+ -29, -31, -35, -38, -41, -45, -49, -53,
+ -58, -63, -68, -73, -79, -85, -91, -97,
+ -104, -111, -117, -125, -132, -139, -147, -154,
+ -161, -169, -176, -183, -190, -196, -202, -208,
+ 213, 218, 222, 225, 227, 228, 228, 227,
+ 224, 221, 215, 208, 200, 189, 177, 163,
+ 146, 127, 106, 83, 57, 29, -2, -36,
+ -72, -111, -153, -197, -244, -294, -347, -401,
+ -459, -519, -581, -645, -711, -779, -848, -919,
+ -991, -1064, -1137, -1210, -1283, -1356, -1428, -1498,
+ -1567, -1634, -1698, -1759, -1817, -1870, -1919, -1962,
+ -2001, -2032, -2057, -2075, -2085, -2087, -2080, -2063,
+ 2037, 2000, 1952, 1893, 1822, 1739, 1644, 1535,
+ 1414, 1280, 1131, 970, 794, 605, 402, 185,
+ -45, -288, -545, -814, -1095, -1388, -1692, -2006,
+ -2330, -2663, -3004, -3351, -3705, -4063, -4425, -4788,
+ -5153, -5517, -5879, -6237, -6589, -6935, -7271, -7597,
+ -7910, -8209, -8491, -8755, -8998, -9219, -9416, -9585,
+ -9727, -9838, -9916, -9959, -9966, -9935, -9863, -9750,
+ -9592, -9389, -9139, -8840, -8492, -8092, -7640, -7134,
+ 6574, 5959, 5288, 4561, 3776, 2935, 2037, 1082,
+ 70, -998, -2122, -3300, -4533, -5818, -7154, -8540,
+ -9975,-11455,-12980,-14548,-16155,-17799,-19478,-21189,
+-22929,-24694,-26482,-28289,-30112,-31947,-33791,-35640,
+-37489,-39336,-41176,-43006,-44821,-46617,-48390,-50137,
+-51853,-53534,-55178,-56778,-58333,-59838,-61289,-62684,
+-64019,-65290,-66494,-67629,-68692,-69679,-70590,-71420,
+-72169,-72835,-73415,-73908,-74313,-74630,-74856,-74992,
+ 75038
+};
+
+void ff_mpa_synth_init(int16 *window) {
+ int i;
+ int32 v;
+
+ // max = 18760, max sum over all 16 coefs : 44736
+ for(i = 0; i < 257; i++) {
+ v = ff_mpa_enwindow[i];
+ v = (v + 2) >> 2;
+ window[i] = v;
+
+ if ((i & 63) != 0)
+ v = -v;
+
+ if (i != 0)
+ window[512 - i] = v;
+ }
+}
+
+static inline uint16 round_sample(int *sum) {
+ int sum1;
+ sum1 = (*sum) >> 14;
+ *sum &= (1 << 14)-1;
+ if (sum1 < (-0x7fff - 1))
+ sum1 = (-0x7fff - 1);
+ if (sum1 > 0x7fff)
+ sum1 = 0x7fff;
+ return sum1;
+}
+
+static inline int MULH(int a, int b) {
+ return ((int64_t)(a) * (int64_t)(b))>>32;
+}
+
+// signed 16x16 -> 32 multiply add accumulate
+#define MACS(rt, ra, rb) rt += (ra) * (rb)
+
+#define MLSS(rt, ra, rb) ((rt) -= (ra) * (rb))
+
+#define SUM8(op, sum, w, p)\
+{\
+ op(sum, (w)[0 * 64], (p)[0 * 64]);\
+ op(sum, (w)[1 * 64], (p)[1 * 64]);\
+ op(sum, (w)[2 * 64], (p)[2 * 64]);\
+ op(sum, (w)[3 * 64], (p)[3 * 64]);\
+ op(sum, (w)[4 * 64], (p)[4 * 64]);\
+ op(sum, (w)[5 * 64], (p)[5 * 64]);\
+ op(sum, (w)[6 * 64], (p)[6 * 64]);\
+ op(sum, (w)[7 * 64], (p)[7 * 64]);\
+}
+
+#define SUM8P2(sum1, op1, sum2, op2, w1, w2, p) \
+{\
+ tmp_s = p[0 * 64];\
+ op1(sum1, (w1)[0 * 64], tmp_s);\
+ op2(sum2, (w2)[0 * 64], tmp_s);\
+ tmp_s = p[1 * 64];\
+ op1(sum1, (w1)[1 * 64], tmp_s);\
+ op2(sum2, (w2)[1 * 64], tmp_s);\
+ tmp_s = p[2 * 64];\
+ op1(sum1, (w1)[2 * 64], tmp_s);\
+ op2(sum2, (w2)[2 * 64], tmp_s);\
+ tmp_s = p[3 * 64];\
+ op1(sum1, (w1)[3 * 64], tmp_s);\
+ op2(sum2, (w2)[3 * 64], tmp_s);\
+ tmp_s = p[4 * 64];\
+ op1(sum1, (w1)[4 * 64], tmp_s);\
+ op2(sum2, (w2)[4 * 64], tmp_s);\
+ tmp_s = p[5 * 64];\
+ op1(sum1, (w1)[5 * 64], tmp_s);\
+ op2(sum2, (w2)[5 * 64], tmp_s);\
+ tmp_s = p[6 * 64];\
+ op1(sum1, (w1)[6 * 64], tmp_s);\
+ op2(sum2, (w2)[6 * 64], tmp_s);\
+ tmp_s = p[7 * 64];\
+ op1(sum1, (w1)[7 * 64], tmp_s);\
+ op2(sum2, (w2)[7 * 64], tmp_s);\
+}
+
+#define FIXHR(a) ((int)((a) * (1LL<<32) + 0.5))
+
+// tab[i][j] = 1.0 / (2.0 * cos(pi*(2*k+1) / 2^(6 - j)))
+
+// cos(i*pi/64)
+
+#define COS0_0 FIXHR(0.50060299823519630134/2)
+#define COS0_1 FIXHR(0.50547095989754365998/2)
+#define COS0_2 FIXHR(0.51544730992262454697/2)
+#define COS0_3 FIXHR(0.53104259108978417447/2)
+#define COS0_4 FIXHR(0.55310389603444452782/2)
+#define COS0_5 FIXHR(0.58293496820613387367/2)
+#define COS0_6 FIXHR(0.62250412303566481615/2)
+#define COS0_7 FIXHR(0.67480834145500574602/2)
+#define COS0_8 FIXHR(0.74453627100229844977/2)
+#define COS0_9 FIXHR(0.83934964541552703873/2)
+#define COS0_10 FIXHR(0.97256823786196069369/2)
+#define COS0_11 FIXHR(1.16943993343288495515/4)
+#define COS0_12 FIXHR(1.48416461631416627724/4)
+#define COS0_13 FIXHR(2.05778100995341155085/8)
+#define COS0_14 FIXHR(3.40760841846871878570/8)
+#define COS0_15 FIXHR(10.19000812354805681150/32)
+
+#define COS1_0 FIXHR(0.50241928618815570551/2)
+#define COS1_1 FIXHR(0.52249861493968888062/2)
+#define COS1_2 FIXHR(0.56694403481635770368/2)
+#define COS1_3 FIXHR(0.64682178335999012954/2)
+#define COS1_4 FIXHR(0.78815462345125022473/2)
+#define COS1_5 FIXHR(1.06067768599034747134/4)
+#define COS1_6 FIXHR(1.72244709823833392782/4)
+#define COS1_7 FIXHR(5.10114861868916385802/16)
+
+#define COS2_0 FIXHR(0.50979557910415916894/2)
+#define COS2_1 FIXHR(0.60134488693504528054/2)
+#define COS2_2 FIXHR(0.89997622313641570463/2)
+#define COS2_3 FIXHR(2.56291544774150617881/8)
+
+#define COS3_0 FIXHR(0.54119610014619698439/2)
+#define COS3_1 FIXHR(1.30656296487637652785/4)
+
+#define COS4_0 FIXHR(0.70710678118654752439/2)
+
+/* butterfly operator */
+#define BF(a, b, c, s)\
+{\
+ tmp0 = tab[a] + tab[b];\
+ tmp1 = tab[a] - tab[b];\
+ tab[a] = tmp0;\
+ tab[b] = MULH(tmp1<<(s), c);\
+}
+
+#define BF1(a, b, c, d)\
+{\
+ BF(a, b, COS4_0, 1);\
+ BF(c, d,-COS4_0, 1);\
+ tab[c] += tab[d];\
+}
+
+#define BF2(a, b, c, d)\
+{\
+ BF(a, b, COS4_0, 1);\
+ BF(c, d,-COS4_0, 1);\
+ tab[c] += tab[d];\
+ tab[a] += tab[c];\
+ tab[c] += tab[b];\
+ tab[b] += tab[d];\
+}
+
+#define ADD(a, b) tab[a] += tab[b]
+
+// DCT32 without 1/sqrt(2) coef zero scaling.
+static void dct32(int32 *out, int32 *tab) {
+ int tmp0, tmp1;
+
+ // pass 1
+ BF( 0, 31, COS0_0 , 1);
+ BF(15, 16, COS0_15, 5);
+ // pass 2
+ BF( 0, 15, COS1_0 , 1);
+ BF(16, 31,-COS1_0 , 1);
+ // pass 1
+ BF( 7, 24, COS0_7 , 1);
+ BF( 8, 23, COS0_8 , 1);
+ // pass 2
+ BF( 7, 8, COS1_7 , 4);
+ BF(23, 24,-COS1_7 , 4);
+ // pass 3
+ BF( 0, 7, COS2_0 , 1);
+ BF( 8, 15,-COS2_0 , 1);
+ BF(16, 23, COS2_0 , 1);
+ BF(24, 31,-COS2_0 , 1);
+ // pass 1
+ BF( 3, 28, COS0_3 , 1);
+ BF(12, 19, COS0_12, 2);
+ // pass 2
+ BF( 3, 12, COS1_3 , 1);
+ BF(19, 28,-COS1_3 , 1);
+ // pass 1
+ BF( 4, 27, COS0_4 , 1);
+ BF(11, 20, COS0_11, 2);
+ // pass 2
+ BF( 4, 11, COS1_4 , 1);
+ BF(20, 27,-COS1_4 , 1);
+ // pass 3
+ BF( 3, 4, COS2_3 , 3);
+ BF(11, 12,-COS2_3 , 3);
+ BF(19, 20, COS2_3 , 3);
+ BF(27, 28,-COS2_3 , 3);
+ // pass 4
+ BF( 0, 3, COS3_0 , 1);
+ BF( 4, 7,-COS3_0 , 1);
+ BF( 8, 11, COS3_0 , 1);
+ BF(12, 15,-COS3_0 , 1);
+ BF(16, 19, COS3_0 , 1);
+ BF(20, 23,-COS3_0 , 1);
+ BF(24, 27, COS3_0 , 1);
+ BF(28, 31,-COS3_0 , 1);
+
+ // pass 1
+ BF( 1, 30, COS0_1 , 1);
+ BF(14, 17, COS0_14, 3);
+ // pass 2
+ BF( 1, 14, COS1_1 , 1);
+ BF(17, 30,-COS1_1 , 1);
+ // pass 1
+ BF( 6, 25, COS0_6 , 1);
+ BF( 9, 22, COS0_9 , 1);
+ // pass 2
+ BF( 6, 9, COS1_6 , 2);
+ BF(22, 25,-COS1_6 , 2);
+ // pass 3
+ BF( 1, 6, COS2_1 , 1);
+ BF( 9, 14,-COS2_1 , 1);
+ BF(17, 22, COS2_1 , 1);
+ BF(25, 30,-COS2_1 , 1);
+
+ // pass 1
+ BF( 2, 29, COS0_2 , 1);
+ BF(13, 18, COS0_13, 3);
+ // pass 2
+ BF( 2, 13, COS1_2 , 1);
+ BF(18, 29,-COS1_2 , 1);
+ // pass 1
+ BF( 5, 26, COS0_5 , 1);
+ BF(10, 21, COS0_10, 1);
+ // pass 2
+ BF( 5, 10, COS1_5 , 2);
+ BF(21, 26,-COS1_5 , 2);
+ // pass 3
+ BF( 2, 5, COS2_2 , 1);
+ BF(10, 13,-COS2_2 , 1);
+ BF(18, 21, COS2_2 , 1);
+ BF(26, 29,-COS2_2 , 1);
+ // pass 4
+ BF( 1, 2, COS3_1 , 2);
+ BF( 5, 6,-COS3_1 , 2);
+ BF( 9, 10, COS3_1 , 2);
+ BF(13, 14,-COS3_1 , 2);
+ BF(17, 18, COS3_1 , 2);
+ BF(21, 22,-COS3_1 , 2);
+ BF(25, 26, COS3_1 , 2);
+ BF(29, 30,-COS3_1 , 2);
+
+ // pass 5
+ BF1( 0, 1, 2, 3);
+ BF2( 4, 5, 6, 7);
+ BF1( 8, 9, 10, 11);
+ BF2(12, 13, 14, 15);
+ BF1(16, 17, 18, 19);
+ BF2(20, 21, 22, 23);
+ BF1(24, 25, 26, 27);
+ BF2(28, 29, 30, 31);
+
+ // pass 6
+ ADD( 8, 12);
+ ADD(12, 10);
+ ADD(10, 14);
+ ADD(14, 9);
+ ADD( 9, 13);
+ ADD(13, 11);
+ ADD(11, 15);
+
+ out[ 0] = tab[0];
+ out[16] = tab[1];
+ out[ 8] = tab[2];
+ out[24] = tab[3];
+ out[ 4] = tab[4];
+ out[20] = tab[5];
+ out[12] = tab[6];
+ out[28] = tab[7];
+ out[ 2] = tab[8];
+ out[18] = tab[9];
+ out[10] = tab[10];
+ out[26] = tab[11];
+ out[ 6] = tab[12];
+ out[22] = tab[13];
+ out[14] = tab[14];
+ out[30] = tab[15];
+
+ ADD(24, 28);
+ ADD(28, 26);
+ ADD(26, 30);
+ ADD(30, 25);
+ ADD(25, 29);
+ ADD(29, 27);
+ ADD(27, 31);
+
+ out[ 1] = tab[16] + tab[24];
+ out[17] = tab[17] + tab[25];
+ out[ 9] = tab[18] + tab[26];
+ out[25] = tab[19] + tab[27];
+ out[ 5] = tab[20] + tab[28];
+ out[21] = tab[21] + tab[29];
+ out[13] = tab[22] + tab[30];
+ out[29] = tab[23] + tab[31];
+ out[ 3] = tab[24] + tab[20];
+ out[19] = tab[25] + tab[21];
+ out[11] = tab[26] + tab[22];
+ out[27] = tab[27] + tab[23];
+ out[ 7] = tab[28] + tab[18];
+ out[23] = tab[29] + tab[19];
+ out[15] = tab[30] + tab[17];
+ out[31] = tab[31];
+}
+
+// 32 sub band synthesis filter. Input: 32 sub band samples, Output:
+// 32 samples.
+// XXX: optimize by avoiding ring buffer usage
+void ff_mpa_synth_filter(int16 *synth_buf_ptr, int *synth_buf_offset,
+ int16 *window, int *dither_state,
+ int16 *samples, int incr,
+ int32 sb_samples[32])
+{
+ int16 *synth_buf;
+ const int16 *w, *w2, *p;
+ int j, offset;
+ int16 *samples2;
+ int32 tmp[32];
+ int sum, sum2;
+ int tmp_s;
+
+ offset = *synth_buf_offset;
+ synth_buf = synth_buf_ptr + offset;
+
+ dct32(tmp, sb_samples);
+ for(j = 0; j < 32; j++) {
+ // NOTE: can cause a loss in precision if very high amplitude sound
+ if (tmp[j] < (-0x7fff - 1))
+ synth_buf[j] = (-0x7fff - 1);
+ else if (tmp[j] > 0x7fff)
+ synth_buf[j] = 0x7fff;
+ else
+ synth_buf[j] = tmp[j];
+ }
+
+ // copy to avoid wrap
+ memcpy(synth_buf + 512, synth_buf, 32 * sizeof(int16));
+
+ samples2 = samples + 31 * incr;
+ w = window;
+ w2 = window + 31;
+
+ sum = *dither_state;
+ p = synth_buf + 16;
+ SUM8(MACS, sum, w, p);
+ p = synth_buf + 48;
+ SUM8(MLSS, sum, w + 32, p);
+ *samples = round_sample(&sum);
+ samples += incr;
+ w++;
+
+ // we calculate two samples at the same time to avoid one memory
+ // access per two sample
+ for(j = 1; j < 16; j++) {
+ sum2 = 0;
+ p = synth_buf + 16 + j;
+ SUM8P2(sum, MACS, sum2, MLSS, w, w2, p);
+ p = synth_buf + 48 - j;
+ SUM8P2(sum, MLSS, sum2, MLSS, w + 32, w2 + 32, p);
+
+ *samples = round_sample(&sum);
+ samples += incr;
+ sum += sum2;
+ *samples2 = round_sample(&sum);
+ samples2 -= incr;
+ w++;
+ w2--;
+ }
+
+ p = synth_buf + 32;
+ SUM8(MLSS, sum, w + 32, p);
+ *samples = round_sample(&sum);
+ *dither_state= sum;
+
+ offset = (offset - 32) & 511;
+ *synth_buf_offset = offset;
+}
+
+/**
+ * parses a vlc code, faster then get_vlc()
+ * @param bits is the number of bits which will be read at once, must be
+ * identical to nb_bits in init_vlc()
+ * @param max_depth is the number of times bits bits must be read to completely
+ * read the longest vlc code
+ * = (max_vlc_length + bits - 1) / bits
+ */
+static int getVlc2(GetBitContext *s, int16 (*table)[2], int bits, int maxDepth) {
+ int reIndex;
+ int reCache;
+ int index;
+ int code;
+ int n;
+
+ debug(1, "int getVlc2(GetBitContext *s, int16 (*table)[2], int bits, int maxDepth)");
+
+ reIndex = s->index;
+ reCache = READ_LE_UINT32(s->buffer + (reIndex >> 3)) >> (reIndex & 0x07);
+ index = reCache & (0xffffffff >> (32 - bits));
+ code = table[index][0];
+ n = table[index][1];
+
+ debug(1, "reIndex : %d", reIndex);
+ debug(1, "reCache : %d", reCache);
+ debug(1, "index : %d", index);
+ debug(1, "code : %d", code);
+ debug(1, "n : %d", n);
+
+ if (maxDepth > 1 && n < 0){
+ reIndex += bits;
+ reCache = READ_LE_UINT32(s->buffer + (reIndex >> 3)) >> (reIndex & 0x07);
+
+ int nbBits = -n;
+
+ index = (reCache & (0xffffffff >> (32 - nbBits))) + code;
+ code = table[index][0];
+ n = table[index][1];
+
+ if(maxDepth > 2 && n < 0) {
+ reIndex += nbBits;
+ reCache = READ_LE_UINT32(s->buffer + (reIndex >> 3)) >> (reIndex & 0x07);
+
+ nbBits = -n;
+
+ index = (reCache & (0xffffffff >> (32 - nbBits))) + code;
+ code = table[index][0];
+ n = table[index][1];
+ }
+ }
+
+ reCache >>= n;
+ s->index = reIndex + n;
+ return code;
+}
+
+static int allocTable(VLC *vlc, int size, int use_static) {
+ int index;
+ index = vlc->table_size;
+ vlc->table_size += size;
+ if (vlc->table_size > vlc->table_allocated) {
+ if(use_static)
+ error("QDM2 cant do anything, init_vlc() is used with too little memory");
+ vlc->table_allocated += (1 << vlc->bits);
+ vlc->table = (int16 (*)[2])realloc(vlc->table, sizeof(int16 *) * 2 * vlc->table_allocated);
+ if (!vlc->table)
+ return -1;
+ }
+ return index;
+}
+
+#define GET_DATA(v, table, i, wrap, size)\
+{\
+ const uint8 *ptr = (const uint8 *)table + i * wrap;\
+ switch(size) {\
+ case 1:\
+ v = *(const uint8 *)ptr;\
+ break;\
+ case 2:\
+ v = *(const uint16 *)ptr;\
+ break;\
+ default:\
+ v = *(const uint32 *)ptr;\
+ break;\
+ }\
+}
+
+static int build_table(VLC *vlc, int table_nb_bits,
+ int nb_codes,
+ const void *bits, int bits_wrap, int bits_size,
+ const void *codes, int codes_wrap, int codes_size,
+ const void *symbols, int symbols_wrap, int symbols_size,
+ int code_prefix, int n_prefix, int flags)
+{
+ int i, j, k, n, table_size, table_index, nb, n1, index, code_prefix2, symbol;
+ uint32 code;
+ int16 (*table)[2];
+
+ table_size = 1 << table_nb_bits;
+ table_index = allocTable(vlc, table_size, flags & 4);
+ debug(2, "QDM2 new table index=%d size=%d code_prefix=%x n=%d", table_index, table_size, code_prefix, n_prefix);
+ if (table_index < 0)
+ return -1;
+ table = &vlc->table[table_index];
+
+ for(i = 0; i < table_size; i++) {
+ table[i][1] = 0; //bits
+ table[i][0] = -1; //codes
+ }
+
+ // first pass: map codes and compute auxillary table sizes
+ for(i = 0; i < nb_codes; i++) {
+ GET_DATA(n, bits, i, bits_wrap, bits_size);
+ GET_DATA(code, codes, i, codes_wrap, codes_size);
+ // we accept tables with holes
+ if (n <= 0)
+ continue;
+ if (!symbols)
+ symbol = i;
+ else
+ GET_DATA(symbol, symbols, i, symbols_wrap, symbols_size);
+ debug(2, "QDM2 i=%d n=%d code=0x%x", i, n, code);
+ // if code matches the prefix, it is in the table
+ n -= n_prefix;
+ if(flags & 2)
+ code_prefix2= code & (n_prefix>=32 ? 0xffffffff : (1 << n_prefix)-1);
+ else
+ code_prefix2= code >> n;
+ if (n > 0 && code_prefix2 == code_prefix) {
+ if (n <= table_nb_bits) {
+ // no need to add another table
+ j = (code << (table_nb_bits - n)) & (table_size - 1);
+ nb = 1 << (table_nb_bits - n);
+ for(k = 0; k < nb; k++) {
+ if(flags & 2)
+ j = (code >> n_prefix) + (k<<n);
+ debug(2, "QDM2 %4x: code=%d n=%d",j, i, n);
+ if (table[j][1] /*bits*/ != 0) {
+ error("QDM2 incorrect codes");
+ return -1;
+ }
+ table[j][1] = n; //bits
+ table[j][0] = symbol;
+ j++;
+ }
+ } else {
+ n -= table_nb_bits;
+ j = (code >> ((flags & 2) ? n_prefix : n)) & ((1 << table_nb_bits) - 1);
+ debug(2, "QDM2 %4x: n=%d (subtable)", j, n);
+ // compute table size
+ n1 = -table[j][1]; //bits
+ if (n > n1)
+ n1 = n;
+ table[j][1] = -n1; //bits
+ }
+ }
+ }
+
+ // second pass : fill auxillary tables recursively
+ for(i = 0;i < table_size; i++) {
+ n = table[i][1]; //bits
+ if (n < 0) {
+ n = -n;
+ if (n > table_nb_bits) {
+ n = table_nb_bits;
+ table[i][1] = -n; //bits
+ }
+ index = build_table(vlc, n, nb_codes,
+ bits, bits_wrap, bits_size,
+ codes, codes_wrap, codes_size,
+ symbols, symbols_wrap, symbols_size,
+ (flags & 2) ? (code_prefix | (i << n_prefix)) : ((code_prefix << table_nb_bits) | i),
+ n_prefix + table_nb_bits, flags);
+ if (index < 0)
+ return -1;
+ // note: realloc has been done, so reload tables
+ table = &vlc->table[table_index];
+ table[i][0] = index; //code
+ }
+ }
+ return table_index;
+}
+
+/* Build VLC decoding tables suitable for use with get_vlc().
+
+ 'nb_bits' set thee decoding table size (2^nb_bits) entries. The
+ bigger it is, the faster is the decoding. But it should not be too
+ big to save memory and L1 cache. '9' is a good compromise.
+
+ 'nb_codes' : number of vlcs codes
+
+ 'bits' : table which gives the size (in bits) of each vlc code.
+
+ 'codes' : table which gives the bit pattern of of each vlc code.
+
+ 'symbols' : table which gives the values to be returned from get_vlc().
+
+ 'xxx_wrap' : give the number of bytes between each entry of the
+ 'bits' or 'codes' tables.
+
+ 'xxx_size' : gives the number of bytes of each entry of the 'bits'
+ or 'codes' tables.
+
+ 'wrap' and 'size' allows to use any memory configuration and types
+ (byte/word/long) to store the 'bits', 'codes', and 'symbols' tables.
+
+ 'use_static' should be set to 1 for tables, which should be freed
+ with av_free_static(), 0 if free_vlc() will be used.
+*/
+void initVlcSparse(VLC *vlc, int nb_bits, int nb_codes,
+ const void *bits, int bits_wrap, int bits_size,
+ const void *codes, int codes_wrap, int codes_size,
+ const void *symbols, int symbols_wrap, int symbols_size) {
+ vlc->bits = nb_bits;
+
+ if(vlc->table_size && vlc->table_size == vlc->table_allocated) {
+ return;
+ } else if(vlc->table_size) {
+ error("called on a partially initialized table");
+ }
+
+ debug(2, "QDM2 build table nb_codes=%d", nb_codes);
+
+ if (build_table(vlc, nb_bits, nb_codes,
+ bits, bits_wrap, bits_size,
+ codes, codes_wrap, codes_size,
+ symbols, symbols_wrap, symbols_size,
+ 0, 0, 4 | 2) < 0) {
+ free(&vlc->table);
+ return; // Error
+ }
+
+ if(vlc->table_size != vlc->table_allocated)
+ error("QDM2 needed %d had %d", vlc->table_size, vlc->table_allocated);
+}
+
+void QDM2Stream::softclipTableInit(void) {
+ uint16 i;
+ double dfl = SOFTCLIP_THRESHOLD - 32767;
+ float delta = 1.0 / -dfl;
+
+ for (i = 0; i < ARRAYSIZE(_softclipTable); i++)
+ _softclipTable[i] = SOFTCLIP_THRESHOLD - ((int)(sin((float)i * delta) * dfl) & 0x0000FFFF);
+}
+
+// random generated table
+void QDM2Stream::rndTableInit(void) {
+ uint16 i;
+ uint16 j;
+ uint32 ldw, hdw;
+ // TODO: Replace Code with uint64 less version...
+ int64_t tmp64_1;
+ int64_t random_seed = 0;
+ float delta = 1.0 / 16384.0;
+
+ for(i = 0; i < ARRAYSIZE(_noiseTable); i++) {
+ random_seed = random_seed * 214013 + 2531011;
+ _noiseTable[i] = (delta * (float)(((int32)random_seed >> 16) & 0x00007FFF)- 1.0) * 1.3;
+ }
+
+ for (i = 0; i < 256; i++) {
+ random_seed = 81;
+ ldw = i;
+ for (j = 0; j < 5; j++) {
+ _randomDequantIndex[i][j] = (uint8)((ldw / random_seed) & 0xFF);
+ ldw = (uint32)ldw % (uint32)random_seed;
+ tmp64_1 = (random_seed * 0x55555556);
+ hdw = (uint32)(tmp64_1 >> 32);
+ random_seed = (int64_t)(hdw + (ldw >> 31));
+ }
+ }
+
+ for (i = 0; i < 128; i++) {
+ random_seed = 25;
+ ldw = i;
+ for (j = 0; j < 3; j++) {
+ _randomDequantType24[i][j] = (uint8)((ldw / random_seed) & 0xFF);
+ ldw = (uint32)ldw % (uint32)random_seed;
+ tmp64_1 = (random_seed * 0x66666667);
+ hdw = (uint32)(tmp64_1 >> 33);
+ random_seed = hdw + (ldw >> 31);
+ }
+ }
+}
+
+void QDM2Stream::initNoiseSamples(void) {
+ uint16 i;
+ uint32 random_seed = 0;
+ float delta = 1.0 / 16384.0;
+
+ for (i = 0; i < ARRAYSIZE(_noiseSamples); i++) {
+ random_seed = random_seed * 214013 + 2531011;
+ _noiseSamples[i] = (delta * (float)((random_seed >> 16) & 0x00007fff) - 1.0);
+ }
+}
+
+static const uint16 qdm2_vlc_offs[18] = {
+ 0, 260, 566, 598, 894, 1166, 1230, 1294, 1678, 1950, 2214, 2278, 2310, 2570, 2834, 3124, 3448, 3838
+};
+
+void QDM2Stream::initVlc(void) {
+ static int16 qdm2_table[3838][2];
+
+ if (!_vlcsInitialized) {
+ _vlcTabLevel.table = &qdm2_table[qdm2_vlc_offs[0]];
+ _vlcTabLevel.table_allocated = qdm2_vlc_offs[1] - qdm2_vlc_offs[0];
+ _vlcTabLevel.table_size = 0;
+ initVlcSparse(&_vlcTabLevel, 8, 24,
+ vlc_tab_level_huffbits, 1, 1,
+ vlc_tab_level_huffcodes, 2, 2, NULL, 0, 0);
+
+ _vlcTabDiff.table = &qdm2_table[qdm2_vlc_offs[1]];
+ _vlcTabDiff.table_allocated = qdm2_vlc_offs[2] - qdm2_vlc_offs[1];
+ _vlcTabDiff.table_size = 0;
+ initVlcSparse(&_vlcTabDiff, 8, 37,
+ vlc_tab_diff_huffbits, 1, 1,
+ vlc_tab_diff_huffcodes, 2, 2, NULL, 0, 0);
+
+ _vlcTabRun.table = &qdm2_table[qdm2_vlc_offs[2]];
+ _vlcTabRun.table_allocated = qdm2_vlc_offs[3] - qdm2_vlc_offs[2];
+ _vlcTabRun.table_size = 0;
+ initVlcSparse(&_vlcTabRun, 5, 6,
+ vlc_tab_run_huffbits, 1, 1,
+ vlc_tab_run_huffcodes, 1, 1, NULL, 0, 0);
+
+ _fftLevelExpAltVlc.table = &qdm2_table[qdm2_vlc_offs[3]];
+ _fftLevelExpAltVlc.table_allocated = qdm2_vlc_offs[4] - qdm2_vlc_offs[3];
+ _fftLevelExpAltVlc.table_size = 0;
+ initVlcSparse(&_fftLevelExpAltVlc, 8, 28,
+ fft_level_exp_alt_huffbits, 1, 1,
+ fft_level_exp_alt_huffcodes, 2, 2, NULL, 0, 0);
+
+ _fftLevelExpVlc.table = &qdm2_table[qdm2_vlc_offs[4]];
+ _fftLevelExpVlc.table_allocated = qdm2_vlc_offs[5] - qdm2_vlc_offs[4];
+ _fftLevelExpVlc.table_size = 0;
+ initVlcSparse(&_fftLevelExpVlc, 8, 20,
+ fft_level_exp_huffbits, 1, 1,
+ fft_level_exp_huffcodes, 2, 2, NULL, 0, 0);
+
+ _fftStereoExpVlc.table = &qdm2_table[qdm2_vlc_offs[5]];
+ _fftStereoExpVlc.table_allocated = qdm2_vlc_offs[6] - qdm2_vlc_offs[5];
+ _fftStereoExpVlc.table_size = 0;
+ initVlcSparse(&_fftStereoExpVlc, 6, 7,
+ fft_stereo_exp_huffbits, 1, 1,
+ fft_stereo_exp_huffcodes, 1, 1, NULL, 0, 0);
+
+ _fftStereoPhaseVlc.table = &qdm2_table[qdm2_vlc_offs[6]];
+ _fftStereoPhaseVlc.table_allocated = qdm2_vlc_offs[7] - qdm2_vlc_offs[6];
+ _fftStereoPhaseVlc.table_size = 0;
+ initVlcSparse(&_fftStereoPhaseVlc, 6, 9,
+ fft_stereo_phase_huffbits, 1, 1,
+ fft_stereo_phase_huffcodes, 1, 1, NULL, 0, 0);
+
+ _vlcTabToneLevelIdxHi1.table = &qdm2_table[qdm2_vlc_offs[7]];
+ _vlcTabToneLevelIdxHi1.table_allocated = qdm2_vlc_offs[8] - qdm2_vlc_offs[7];
+ _vlcTabToneLevelIdxHi1.table_size = 0;
+ initVlcSparse(&_vlcTabToneLevelIdxHi1, 8, 20,
+ vlc_tab_tone_level_idx_hi1_huffbits, 1, 1,
+ vlc_tab_tone_level_idx_hi1_huffcodes, 2, 2, NULL, 0, 0);
+
+ _vlcTabToneLevelIdxMid.table = &qdm2_table[qdm2_vlc_offs[8]];
+ _vlcTabToneLevelIdxMid.table_allocated = qdm2_vlc_offs[9] - qdm2_vlc_offs[8];
+ _vlcTabToneLevelIdxMid.table_size = 0;
+ initVlcSparse(&_vlcTabToneLevelIdxMid, 8, 24,
+ vlc_tab_tone_level_idx_mid_huffbits, 1, 1,
+ vlc_tab_tone_level_idx_mid_huffcodes, 2, 2, NULL, 0, 0);
+
+ _vlcTabToneLevelIdxHi2.table = &qdm2_table[qdm2_vlc_offs[9]];
+ _vlcTabToneLevelIdxHi2.table_allocated = qdm2_vlc_offs[10] - qdm2_vlc_offs[9];
+ _vlcTabToneLevelIdxHi2.table_size = 0;
+ initVlcSparse(&_vlcTabToneLevelIdxHi2, 8, 24,
+ vlc_tab_tone_level_idx_hi2_huffbits, 1, 1,
+ vlc_tab_tone_level_idx_hi2_huffcodes, 2, 2, NULL, 0, 0);
+
+ _vlcTabType30.table = &qdm2_table[qdm2_vlc_offs[10]];
+ _vlcTabType30.table_allocated = qdm2_vlc_offs[11] - qdm2_vlc_offs[10];
+ _vlcTabType30.table_size = 0;
+ initVlcSparse(&_vlcTabType30, 6, 9,
+ vlc_tab_type30_huffbits, 1, 1,
+ vlc_tab_type30_huffcodes, 1, 1, NULL, 0, 0);
+
+ _vlcTabType34.table = &qdm2_table[qdm2_vlc_offs[11]];
+ _vlcTabType34.table_allocated = qdm2_vlc_offs[12] - qdm2_vlc_offs[11];
+ _vlcTabType34.table_size = 0;
+ initVlcSparse(&_vlcTabType34, 5, 10,
+ vlc_tab_type34_huffbits, 1, 1,
+ vlc_tab_type34_huffcodes, 1, 1, NULL, 0, 0);
+
+ _vlcTabFftToneOffset[0].table = &qdm2_table[qdm2_vlc_offs[12]];
+ _vlcTabFftToneOffset[0].table_allocated = qdm2_vlc_offs[13] - qdm2_vlc_offs[12];
+ _vlcTabFftToneOffset[0].table_size = 0;
+ initVlcSparse(&_vlcTabFftToneOffset[0], 8, 23,
+ vlc_tab_fft_tone_offset_0_huffbits, 1, 1,
+ vlc_tab_fft_tone_offset_0_huffcodes, 2, 2, NULL, 0, 0);
+
+ _vlcTabFftToneOffset[1].table = &qdm2_table[qdm2_vlc_offs[13]];
+ _vlcTabFftToneOffset[1].table_allocated = qdm2_vlc_offs[14] - qdm2_vlc_offs[13];
+ _vlcTabFftToneOffset[1].table_size = 0;
+ initVlcSparse(&_vlcTabFftToneOffset[1], 8, 28,
+ vlc_tab_fft_tone_offset_1_huffbits, 1, 1,
+ vlc_tab_fft_tone_offset_1_huffcodes, 2, 2, NULL, 0, 0);
+
+ _vlcTabFftToneOffset[2].table = &qdm2_table[qdm2_vlc_offs[14]];
+ _vlcTabFftToneOffset[2].table_allocated = qdm2_vlc_offs[15] - qdm2_vlc_offs[14];
+ _vlcTabFftToneOffset[2].table_size = 0;
+ initVlcSparse(&_vlcTabFftToneOffset[2], 8, 32,
+ vlc_tab_fft_tone_offset_2_huffbits, 1, 1,
+ vlc_tab_fft_tone_offset_2_huffcodes, 2, 2, NULL, 0, 0);
+
+ _vlcTabFftToneOffset[3].table = &qdm2_table[qdm2_vlc_offs[15]];
+ _vlcTabFftToneOffset[3].table_allocated = qdm2_vlc_offs[16] - qdm2_vlc_offs[15];
+ _vlcTabFftToneOffset[3].table_size = 0;
+ initVlcSparse(&_vlcTabFftToneOffset[3], 8, 35,
+ vlc_tab_fft_tone_offset_3_huffbits, 1, 1,
+ vlc_tab_fft_tone_offset_3_huffcodes, 2, 2, NULL, 0, 0);
+
+ _vlcTabFftToneOffset[4].table = &qdm2_table[qdm2_vlc_offs[16]];
+ _vlcTabFftToneOffset[4].table_allocated = qdm2_vlc_offs[17] - qdm2_vlc_offs[16];
+ _vlcTabFftToneOffset[4].table_size = 0;
+ initVlcSparse(&_vlcTabFftToneOffset[4], 8, 38,
+ vlc_tab_fft_tone_offset_4_huffbits, 1, 1,
+ vlc_tab_fft_tone_offset_4_huffcodes, 2, 2, NULL, 0, 0);
+
+ _vlcsInitialized = true;
+ }
+}
+
+QDM2Stream::QDM2Stream(Common::SeekableReadStream *stream, Common::SeekableReadStream *extraData) {
+ uint32 tmp;
+ int32 tmp_s;
+ int tmp_val;
+ int i;
+
+ debug(1, "QDM2Stream::QDM2Stream() Call");
+
+ _stream = stream;
+ _compressedData = NULL;
+ _subPacket = 0;
+ memset(_quantizedCoeffs, 0, sizeof(_quantizedCoeffs));
+ memset(_fftLevelExp, 0, sizeof(_fftLevelExp));
+ _noiseIdx = 0;
+ memset(_fftCoefsMinIndex, 0, sizeof(_fftCoefsMinIndex));
+ memset(_fftCoefsMaxIndex, 0, sizeof(_fftCoefsMaxIndex));
+ _fftToneStart = 0;
+ _fftToneEnd = 0;
+ for(i = 0; i < ARRAYSIZE(_subPacketListA); i++) {
+ _subPacketListA[i].packet = NULL;
+ _subPacketListA[i].next = NULL;
+ }
+ _subPacketsB = 0;
+ for(i = 0; i < ARRAYSIZE(_subPacketListB); i++) {
+ _subPacketListB[i].packet = NULL;
+ _subPacketListB[i].next = NULL;
+ }
+ for(i = 0; i < ARRAYSIZE(_subPacketListC); i++) {
+ _subPacketListC[i].packet = NULL;
+ _subPacketListC[i].next = NULL;
+ }
+ for(i = 0; i < ARRAYSIZE(_subPacketListD); i++) {
+ _subPacketListD[i].packet = NULL;
+ _subPacketListD[i].next = NULL;
+ }
+ memset(_synthBuf, 0, sizeof(_synthBuf));
+ memset(_synthBufOffset, 0, sizeof(_synthBufOffset));
+ memset(_sbSamples, 0, sizeof(_sbSamples));
+ memset(_outputBuffer, 0, sizeof(_outputBuffer));
+ _vlcsInitialized = false;
+ _superblocktype_2_3 = 0;
+ _hasErrors = false;
+
+ // Rewind extraData stream from any previous calls...
+ extraData->seek(0, SEEK_SET);
+
+ tmp_s = extraData->readSint32BE();
+ debug(1, "QDM2Stream::QDM2Stream() extraSize: %d", tmp_s);
+ if ((extraData->size() - extraData->pos()) / 4 + 1 != tmp_s)
+ warning("QDM2Stream::QDM2Stream() extraSize mismatch - Expected %d", (extraData->size() - extraData->pos()) / 4 + 1);
+ if (tmp_s < 12)
+ error("QDM2Stream::QDM2Stream() Insufficient extraData");
+
+ tmp = extraData->readUint32BE();
+ debug(1, "QDM2Stream::QDM2Stream() extraTag: %d", tmp);
+ if (tmp != MKID_BE('frma'))
+ warning("QDM2Stream::QDM2Stream() extraTag mismatch");
+
+ tmp = extraData->readUint32BE();
+ debug(1, "QDM2Stream::QDM2Stream() extraType: %d", tmp);
+ if (tmp == MKID_BE('QDMC'))
+ warning("QDM2Stream::QDM2Stream() QDMC stream type not supported.");
+ else if (tmp != MKID_BE('QDM2'))
+ error("QDM2Stream::QDM2Stream() Unsupported stream type");
+
+ tmp_s = extraData->readSint32BE();
+ debug(1, "QDM2Stream::QDM2Stream() extraSize2: %d", tmp_s);
+ if ((extraData->size() - extraData->pos()) + 4 != tmp_s)
+ warning("QDM2Stream::QDM2Stream() extraSize2 mismatch - Expected %d", (extraData->size() - extraData->pos()) + 4);
+
+ tmp = extraData->readUint32BE();
+ debug(1, "QDM2Stream::QDM2Stream() extraTag2: %d", tmp);
+ if (tmp != MKID_BE('QDCA'))
+ warning("QDM2Stream::QDM2Stream() extraTag2 mismatch");
+
+ if (extraData->readUint32BE() != 1)
+ warning("QDM2Stream::QDM2Stream() u0 field not 1");
+
+ _channels = extraData->readUint32BE();
+ debug(1, "QDM2Stream::QDM2Stream() channels: %d", _channels);
+
+ _sampleRate = extraData->readUint32BE();
+ debug(1, "QDM2Stream::QDM2Stream() sampleRate: %d", _sampleRate);
+
+ _bitRate = extraData->readUint32BE();
+ debug(1, "QDM2Stream::QDM2Stream() bitRate: %d", _bitRate);
+
+ _blockSize = extraData->readUint32BE();
+ debug(1, "QDM2Stream::QDM2Stream() blockSize: %d", _blockSize);
+
+ _frameSize = extraData->readUint32BE();
+ debug(1, "QDM2Stream::QDM2Stream() frameSize: %d", _frameSize);
+
+ _packetSize = extraData->readUint32BE();
+ debug(1, "QDM2Stream::QDM2Stream() packetSize: %d", _packetSize);
+
+ if (extraData->size() - extraData->pos() != 0) {
+ tmp_s = extraData->readSint32BE();
+ debug(1, "QDM2Stream::QDM2Stream() extraSize3: %d", tmp_s);
+ if (extraData->size() + 4 != tmp_s)
+ warning("QDM2Stream::QDM2Stream() extraSize3 mismatch - Expected %d", extraData->size() + 4);
+
+ tmp = extraData->readUint32BE();
+ debug(1, "QDM2Stream::QDM2Stream() extraTag3: %d", tmp);
+ if (tmp != MKID_BE('QDCP'))
+ warning("QDM2Stream::QDM2Stream() extraTag3 mismatch");
+
+ if ((float)extraData->readUint32BE() != 1.0)
+ warning("QDM2Stream::QDM2Stream() uf0 field not 1.0");
+
+ if (extraData->readUint32BE() != 0)
+ warning("QDM2Stream::QDM2Stream() u1 field not 0");
+
+ if ((float)extraData->readUint32BE() != 1.0)
+ warning("QDM2Stream::QDM2Stream() uf1 field not 1.0");
+
+ if ((float)extraData->readUint32BE() != 1.0)
+ warning("QDM2Stream::QDM2Stream() uf2 field not 1.0");
+
+ if (extraData->readUint32BE() != 27)
+ warning("QDM2Stream::QDM2Stream() u2 field not 27");
+
+ if (extraData->readUint32BE() != 8)
+ warning("QDM2Stream::QDM2Stream() u3 field not 8");
+
+ if (extraData->readUint32BE() != 0)
+ warning("QDM2Stream::QDM2Stream() u4 field not 0");
+ }
+
+ _fftOrder = scummvm_log2(_frameSize) + 1;
+ _fftFrameSize = 2 * _frameSize; // complex has two floats
+
+ // something like max decodable tones
+ _groupOrder = scummvm_log2(_blockSize) + 1;
+ _sFrameSize = _blockSize / 16; // 16 iterations per super block
+
+ _subSampling = _fftOrder - 7;
+ _frequencyRange = 255 / (1 << (2 - _subSampling));
+
+ switch ((_subSampling * 2 + _channels - 1)) {
+ case 0:
+ tmp = 40;
+ break;
+ case 1:
+ tmp = 48;
+ break;
+ case 2:
+ tmp = 56;
+ break;
+ case 3:
+ tmp = 72;
+ break;
+ case 4:
+ tmp = 80;
+ break;
+ case 5:
+ tmp = 100;
+ break;
+ default:
+ tmp = _subSampling;
+ break;
+ }
+
+ tmp_val = 0;
+ if ((tmp * 1000) < _bitRate) tmp_val = 1;
+ if ((tmp * 1440) < _bitRate) tmp_val = 2;
+ if ((tmp * 1760) < _bitRate) tmp_val = 3;
+ if ((tmp * 2240) < _bitRate) tmp_val = 4;
+ _cmTableSelect = tmp_val;
+
+ if (_subSampling == 0)
+ tmp = 7999;
+ else
+ tmp = ((-(_subSampling -1)) & 8000) + 20000;
+
+ if (tmp < 8000)
+ _coeffPerSbSelect = 0;
+ else if (tmp <= 16000)
+ _coeffPerSbSelect = 1;
+ else
+ _coeffPerSbSelect = 2;
+
+ if (_fftOrder < 7 || _fftOrder > 9)
+ error("QDM2Stream::QDM2Stream() Unsupported fft_order: %d", _fftOrder);
+
+ rdftInit(&_rdftCtx, _fftOrder, IRDFT);
+
+ initVlc();
+ ff_mpa_synth_init(ff_mpa_synth_window);
+ softclipTableInit();
+ rndTableInit();
+ initNoiseSamples();
+
+ _compressedData = new uint8[_packetSize];
+}
+
+QDM2Stream::~QDM2Stream() {
+ delete[] _compressedData;
+ delete _stream;
+}
+
+static int qdm2_get_vlc(GetBitContext *gb, VLC *vlc, int flag, int depth) {
+ int value = getVlc2(gb, vlc->table, vlc->bits, depth);
+
+ // stage-2, 3 bits exponent escape sequence
+ if (value-- == 0)
+ value = getBits(gb, getBits (gb, 3) + 1);
+
+ // stage-3, optional
+ if (flag) {
+ int tmp = vlc_stage3_values[value];
+
+ if ((value & ~3) > 0)
+ tmp += getBits(gb, (value >> 2));
+ value = tmp;
+ }
+
+ return value;
+}
+
+static int qdm2_get_se_vlc(VLC *vlc, GetBitContext *gb, int depth)
+{
+ int value = qdm2_get_vlc(gb, vlc, 0, depth);
+
+ return (value & 1) ? ((value + 1) >> 1) : -(value >> 1);
+}
+
+/**
+ * QDM2 checksum
+ *
+ * @param data pointer to data to be checksum'ed
+ * @param length data length
+ * @param value checksum value
+ *
+ * @return 0 if checksum is OK
+ */
+static uint16 qdm2_packet_checksum(const uint8 *data, int length, int value) {
+ int i;
+
+ for (i = 0; i < length; i++)
+ value -= data[i];
+
+ return (uint16)(value & 0xffff);
+}
+
+/**
+ * Fills a QDM2SubPacket structure with packet type, size, and data pointer.
+ *
+ * @param gb bitreader context
+ * @param sub_packet packet under analysis
+ */
+static void qdm2_decode_sub_packet_header(GetBitContext *gb, QDM2SubPacket *sub_packet)
+{
+ sub_packet->type = getBits (gb, 8);
+
+ if (sub_packet->type == 0) {
+ sub_packet->size = 0;
+ sub_packet->data = NULL;
+ } else {
+ sub_packet->size = getBits (gb, 8);
+
+ if (sub_packet->type & 0x80) {
+ sub_packet->size <<= 8;
+ sub_packet->size |= getBits (gb, 8);
+ sub_packet->type &= 0x7f;
+ }
+
+ if (sub_packet->type == 0x7f)
+ sub_packet->type |= (getBits (gb, 8) << 8);
+
+ sub_packet->data = &gb->buffer[getBitsCount(gb) / 8]; // FIXME: this depends on bitreader internal data
+ }
+
+ debug(1, "QDM2 Subpacket: type=%d size=%d start_offs=%x", sub_packet->type, sub_packet->size, getBitsCount(gb) / 8);
+}
+
+/**
+ * Return node pointer to first packet of requested type in list.
+ *
+ * @param list list of subpackets to be scanned
+ * @param type type of searched subpacket
+ * @return node pointer for subpacket if found, else NULL
+ */
+static QDM2SubPNode* qdm2_search_subpacket_type_in_list(QDM2SubPNode *list, int type)
+{
+ while (list != NULL && list->packet != NULL) {
+ if (list->packet->type == type)
+ return list;
+ list = list->next;
+ }
+ return NULL;
+}
+
+/**
+ * Replaces 8 elements with their average value.
+ * Called by qdm2_decode_superblock before starting subblock decoding.
+ */
+void QDM2Stream::average_quantized_coeffs(void) {
+ int i, j, n, ch, sum;
+
+ n = coeff_per_sb_for_avg[_coeffPerSbSelect][QDM2_SB_USED(_subSampling) - 1] + 1;
+
+ for (ch = 0; ch < _channels; ch++) {
+ for (i = 0; i < n; i++) {
+ sum = 0;
+
+ for (j = 0; j < 8; j++)
+ sum += _quantizedCoeffs[ch][i][j];
+
+ sum /= 8;
+ if (sum > 0)
+ sum--;
+
+ for (j = 0; j < 8; j++)
+ _quantizedCoeffs[ch][i][j] = sum;
+ }
+ }
+}
+
+/**
+ * Build subband samples with noise weighted by q->tone_level.
+ * Called by synthfilt_build_sb_samples.
+ *
+ * @param sb subband index
+ */
+void QDM2Stream::build_sb_samples_from_noise(int sb) {
+ int ch, j;
+
+ FIX_NOISE_IDX(_noiseIdx);
+
+ if (!_channels)
+ return;
+
+ for (ch = 0; ch < _channels; ch++) {
+ for (j = 0; j < 64; j++) {
+ _sbSamples[ch][j * 2][sb] = (int32)(SB_DITHERING_NOISE(sb, _noiseIdx) * _toneLevel[ch][sb][j] + .5);
+ _sbSamples[ch][j * 2 + 1][sb] = (int32)(SB_DITHERING_NOISE(sb, _noiseIdx) * _toneLevel[ch][sb][j] + .5);
+ }
+ }
+}
+
+/**
+ * Called while processing data from subpackets 11 and 12.
+ * Used after making changes to coding_method array.
+ *
+ * @param sb subband index
+ * @param channels number of channels
+ * @param coding_method q->coding_method[0][0][0]
+ */
+void QDM2Stream::fix_coding_method_array(int sb, int channels, sb_int8_array coding_method)
+{
+ int j, k;
+ int ch;
+ int run, case_val;
+ int switchtable[23] = {0,5,1,5,5,5,5,5,2,5,5,5,5,5,5,5,3,5,5,5,5,5,4};
+
+ for (ch = 0; ch < channels; ch++) {
+ for (j = 0; j < 64; ) {
+ if((coding_method[ch][sb][j] - 8) > 22) {
+ run = 1;
+ case_val = 8;
+ } else {
+ switch (switchtable[coding_method[ch][sb][j]-8]) {
+ case 0: run = 10; case_val = 10; break;
+ case 1: run = 1; case_val = 16; break;
+ case 2: run = 5; case_val = 24; break;
+ case 3: run = 3; case_val = 30; break;
+ case 4: run = 1; case_val = 30; break;
+ case 5: run = 1; case_val = 8; break;
+ default: run = 1; case_val = 8; break;
+ }
+ }
+ for (k = 0; k < run; k++)
+ if (j + k < 128)
+ if (coding_method[ch][sb + (j + k) / 64][(j + k) % 64] > coding_method[ch][sb][j])
+ if (k > 0) {
+ warning("QDM2 Untested Code: not debugged, almost never used");
+ memset(&coding_method[ch][sb][j + k], case_val, k * sizeof(int8));
+ memset(&coding_method[ch][sb][j + k], case_val, 3 * sizeof(int8));
+ }
+ j += run;
+ }
+ }
+}
+
+/**
+ * Related to synthesis filter
+ * Called by process_subpacket_10
+ *
+ * @param flag 1 if called after getting data from subpacket 10, 0 if no subpacket 10
+ */
+void QDM2Stream::fill_tone_level_array(int flag) {
+ int i, sb, ch, sb_used;
+ int tmp, tab;
+
+ // This should never happen
+ if (_channels <= 0)
+ return;
+
+ for (ch = 0; ch < _channels; ch++) {
+ for (sb = 0; sb < 30; sb++) {
+ for (i = 0; i < 8; i++) {
+ if ((tab=coeff_per_sb_for_dequant[_coeffPerSbSelect][sb]) < (last_coeff[_coeffPerSbSelect] - 1))
+ tmp = _quantizedCoeffs[ch][tab + 1][i] * dequant_table[_coeffPerSbSelect][tab + 1][sb]+
+ _quantizedCoeffs[ch][tab][i] * dequant_table[_coeffPerSbSelect][tab][sb];
+ else
+ tmp = _quantizedCoeffs[ch][tab][i] * dequant_table[_coeffPerSbSelect][tab][sb];
+ if(tmp < 0)
+ tmp += 0xff;
+ _toneLevelIdxBase[ch][sb][i] = (tmp / 256) & 0xff;
+ }
+ }
+ }
+
+ sb_used = QDM2_SB_USED(_subSampling);
+
+ if ((_superblocktype_2_3 != 0) && !flag) {
+ for (sb = 0; sb < sb_used; sb++) {
+ for (ch = 0; ch < _channels; ch++) {
+ for (i = 0; i < 64; i++) {
+ _toneLevelIdx[ch][sb][i] = _toneLevelIdxBase[ch][sb][i / 8];
+ if (_toneLevelIdx[ch][sb][i] < 0)
+ _toneLevel[ch][sb][i] = 0;
+ else
+ _toneLevel[ch][sb][i] = fft_tone_level_table[0][_toneLevelIdx[ch][sb][i] & 0x3f];
+ }
+ }
+ }
+ } else {
+ tab = _superblocktype_2_3 ? 0 : 1;
+ for (sb = 0; sb < sb_used; sb++) {
+ if ((sb >= 4) && (sb <= 23)) {
+ for (ch = 0; ch < _channels; ch++) {
+ for (i = 0; i < 64; i++) {
+ tmp = _toneLevelIdxBase[ch][sb][i / 8] -
+ _toneLevelIdxHi1[ch][sb / 8][i / 8][i % 8] -
+ _toneLevelIdxMid[ch][sb - 4][i / 8] -
+ _toneLevelIdxHi2[ch][sb - 4];
+ _toneLevelIdx[ch][sb][i] = tmp & 0xff;
+ if ((tmp < 0) || (!_superblocktype_2_3 && !tmp))
+ _toneLevel[ch][sb][i] = 0;
+ else
+ _toneLevel[ch][sb][i] = fft_tone_level_table[tab][tmp & 0x3f];
+ }
+ }
+ } else {
+ if (sb > 4) {
+ for (ch = 0; ch < _channels; ch++) {
+ for (i = 0; i < 64; i++) {
+ tmp = _toneLevelIdxBase[ch][sb][i / 8] -
+ _toneLevelIdxHi1[ch][2][i / 8][i % 8] -
+ _toneLevelIdxHi2[ch][sb - 4];
+ _toneLevelIdx[ch][sb][i] = tmp & 0xff;
+ if ((tmp < 0) || (!_superblocktype_2_3 && !tmp))
+ _toneLevel[ch][sb][i] = 0;
+ else
+ _toneLevel[ch][sb][i] = fft_tone_level_table[tab][tmp & 0x3f];
+ }
+ }
+ } else {
+ for (ch = 0; ch < _channels; ch++) {
+ for (i = 0; i < 64; i++) {
+ tmp = _toneLevelIdx[ch][sb][i] = _toneLevelIdxBase[ch][sb][i / 8];
+ if ((tmp < 0) || (!_superblocktype_2_3 && !tmp))
+ _toneLevel[ch][sb][i] = 0;
+ else
+ _toneLevel[ch][sb][i] = fft_tone_level_table[tab][tmp & 0x3f];
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Related to synthesis filter
+ * Called by process_subpacket_11
+ * c is built with data from subpacket 11
+ * Most of this function is used only if superblock_type_2_3 == 0, never seen it in samples
+ *
+ * @param tone_level_idx
+ * @param tone_level_idx_temp
+ * @param coding_method q->coding_method[0][0][0]
+ * @param nb_channels number of channels
+ * @param c coming from subpacket 11, passed as 8*c
+ * @param superblocktype_2_3 flag based on superblock packet type
+ * @param cm_table_select q->cm_table_select
+ */
+void QDM2Stream::fill_coding_method_array(sb_int8_array tone_level_idx, sb_int8_array tone_level_idx_temp,
+ sb_int8_array coding_method, int nb_channels,
+ int c, int superblocktype_2_3, int cm_table_select) {
+ int ch, sb, j;
+ int tmp, acc, esp_40, comp;
+ int add1, add2, add3, add4;
+ // TODO : Remove multres 64 bit variable necessity...
+ int64_t multres;
+
+ // This should never happen
+ if (nb_channels <= 0)
+ return;
+ if (!superblocktype_2_3) {
+ warning("QDM2 This case is untested, no samples available");
+ for (ch = 0; ch < nb_channels; ch++)
+ for (sb = 0; sb < 30; sb++) {
+ for (j = 1; j < 63; j++) { // The loop only iterates to 63 so the code doesn't overflow the buffer
+ add1 = tone_level_idx[ch][sb][j] - 10;
+ if (add1 < 0)
+ add1 = 0;
+ add2 = add3 = add4 = 0;
+ if (sb > 1) {
+ add2 = tone_level_idx[ch][sb - 2][j] + tone_level_idx_offset_table[sb][0] - 6;
+ if (add2 < 0)
+ add2 = 0;
+ }
+ if (sb > 0) {
+ add3 = tone_level_idx[ch][sb - 1][j] + tone_level_idx_offset_table[sb][1] - 6;
+ if (add3 < 0)
+ add3 = 0;
+ }
+ if (sb < 29) {
+ add4 = tone_level_idx[ch][sb + 1][j] + tone_level_idx_offset_table[sb][3] - 6;
+ if (add4 < 0)
+ add4 = 0;
+ }
+ tmp = tone_level_idx[ch][sb][j + 1] * 2 - add4 - add3 - add2 - add1;
+ if (tmp < 0)
+ tmp = 0;
+ tone_level_idx_temp[ch][sb][j + 1] = tmp & 0xff;
+ }
+ tone_level_idx_temp[ch][sb][0] = tone_level_idx_temp[ch][sb][1];
+ }
+ acc = 0;
+ for (ch = 0; ch < nb_channels; ch++)
+ for (sb = 0; sb < 30; sb++)
+ for (j = 0; j < 64; j++)
+ acc += tone_level_idx_temp[ch][sb][j];
+
+ multres = 0x66666667 * (acc * 10);
+ esp_40 = (multres >> 32) / 8 + ((multres & 0xffffffff) >> 31);
+ for (ch = 0; ch < nb_channels; ch++)
+ for (sb = 0; sb < 30; sb++)
+ for (j = 0; j < 64; j++) {
+ comp = tone_level_idx_temp[ch][sb][j]* esp_40 * 10;
+ if (comp < 0)
+ comp += 0xff;
+ comp /= 256; // signed shift
+ switch(sb) {
+ case 0:
+ if (comp < 30)
+ comp = 30;
+ comp += 15;
+ break;
+ case 1:
+ if (comp < 24)
+ comp = 24;
+ comp += 10;
+ break;
+ case 2:
+ case 3:
+ case 4:
+ if (comp < 16)
+ comp = 16;
+ }
+ if (comp <= 5)
+ tmp = 0;
+ else if (comp <= 10)
+ tmp = 10;
+ else if (comp <= 16)
+ tmp = 16;
+ else if (comp <= 24)
+ tmp = -1;
+ else
+ tmp = 0;
+ coding_method[ch][sb][j] = ((tmp & 0xfffa) + 30 )& 0xff;
+ }
+ for (sb = 0; sb < 30; sb++)
+ fix_coding_method_array(sb, nb_channels, coding_method);
+ for (ch = 0; ch < nb_channels; ch++)
+ for (sb = 0; sb < 30; sb++)
+ for (j = 0; j < 64; j++)
+ if (sb >= 10) {
+ if (coding_method[ch][sb][j] < 10)
+ coding_method[ch][sb][j] = 10;
+ } else {
+ if (sb >= 2) {
+ if (coding_method[ch][sb][j] < 16)
+ coding_method[ch][sb][j] = 16;
+ } else {
+ if (coding_method[ch][sb][j] < 30)
+ coding_method[ch][sb][j] = 30;
+ }
+ }
+ } else { // superblocktype_2_3 != 0
+ for (ch = 0; ch < nb_channels; ch++)
+ for (sb = 0; sb < 30; sb++)
+ for (j = 0; j < 64; j++)
+ coding_method[ch][sb][j] = coding_method_table[cm_table_select][sb];
+ }
+}
+
+/**
+ *
+ * Called by process_subpacket_11 to process more data from subpacket 11 with sb 0-8
+ * Called by process_subpacket_12 to process data from subpacket 12 with sb 8-sb_used
+ *
+ * @param gb bitreader context
+ * @param length packet length in bits
+ * @param sb_min lower subband processed (sb_min included)
+ * @param sb_max higher subband processed (sb_max excluded)
+ */
+void QDM2Stream::synthfilt_build_sb_samples(GetBitContext *gb, int length, int sb_min, int sb_max) {
+ int sb, j, k, n, ch, run, channels;
+ int joined_stereo, zero_encoding, chs;
+ int type34_first;
+ float type34_div = 0;
+ float type34_predictor;
+ float samples[10], sign_bits[16];
+
+ if (length == 0) {
+ // If no data use noise
+ for (sb = sb_min; sb < sb_max; sb++)
+ build_sb_samples_from_noise(sb);
+
+ return;
+ }
+
+ for (sb = sb_min; sb < sb_max; sb++) {
+ FIX_NOISE_IDX(_noiseIdx);
+
+ channels = _channels;
+
+ if (_channels <= 1 || sb < 12)
+ joined_stereo = 0;
+ else if (sb >= 24)
+ joined_stereo = 1;
+ else
+ joined_stereo = (BITS_LEFT(length,gb) >= 1) ? getBits1 (gb) : 0;
+
+ if (joined_stereo) {
+ if (BITS_LEFT(length,gb) >= 16)
+ for (j = 0; j < 16; j++)
+ sign_bits[j] = getBits1(gb);
+
+ for (j = 0; j < 64; j++)
+ if (_codingMethod[1][sb][j] > _codingMethod[0][sb][j])
+ _codingMethod[0][sb][j] = _codingMethod[1][sb][j];
+
+ fix_coding_method_array(sb, _channels, _codingMethod);
+ channels = 1;
+ }
+
+ for (ch = 0; ch < channels; ch++) {
+ zero_encoding = (BITS_LEFT(length,gb) >= 1) ? getBits1(gb) : 0;
+ type34_predictor = 0.0;
+ type34_first = 1;
+
+ for (j = 0; j < 128; ) {
+ switch (_codingMethod[ch][sb][j / 2]) {
+ case 8:
+ if (BITS_LEFT(length,gb) >= 10) {
+ if (zero_encoding) {
+ for (k = 0; k < 5; k++) {
+ if ((j + 2 * k) >= 128)
+ break;
+ samples[2 * k] = getBits1(gb) ? dequant_1bit[joined_stereo][2 * getBits1(gb)] : 0;
+ }
+ } else {
+ n = getBits(gb, 8);
+ for (k = 0; k < 5; k++)
+ samples[2 * k] = dequant_1bit[joined_stereo][_randomDequantIndex[n][k]];
+ }
+ for (k = 0; k < 5; k++)
+ samples[2 * k + 1] = SB_DITHERING_NOISE(sb, _noiseIdx);
+ } else {
+ for (k = 0; k < 10; k++)
+ samples[k] = SB_DITHERING_NOISE(sb, _noiseIdx);
+ }
+ run = 10;
+ break;
+
+ case 10:
+ if (BITS_LEFT(length,gb) >= 1) {
+ double f = 0.81;
+
+ if (getBits1(gb))
+ f = -f;
+ f -= _noiseSamples[((sb + 1) * (j +5 * ch + 1)) & 127] * 9.0 / 40.0;
+ samples[0] = f;
+ } else {
+ samples[0] = SB_DITHERING_NOISE(sb, _noiseIdx);
+ }
+ run = 1;
+ break;
+
+ case 16:
+ if (BITS_LEFT(length,gb) >= 10) {
+ if (zero_encoding) {
+ for (k = 0; k < 5; k++) {
+ if ((j + k) >= 128)
+ break;
+ samples[k] = (getBits1(gb) == 0) ? 0 : dequant_1bit[joined_stereo][2 * getBits1(gb)];
+ }
+ } else {
+ n = getBits (gb, 8);
+ for (k = 0; k < 5; k++)
+ samples[k] = dequant_1bit[joined_stereo][_randomDequantIndex[n][k]];
+ }
+ } else {
+ for (k = 0; k < 5; k++)
+ samples[k] = SB_DITHERING_NOISE(sb, _noiseIdx);
+ }
+ run = 5;
+ break;
+
+ case 24:
+ if (BITS_LEFT(length,gb) >= 7) {
+ n = getBits(gb, 7);
+ for (k = 0; k < 3; k++)
+ samples[k] = (_randomDequantType24[n][k] - 2.0) * 0.5;
+ } else {
+ for (k = 0; k < 3; k++)
+ samples[k] = SB_DITHERING_NOISE(sb, _noiseIdx);
+ }
+ run = 3;
+ break;
+
+ case 30:
+ if (BITS_LEFT(length,gb) >= 4)
+ samples[0] = type30_dequant[qdm2_get_vlc(gb, &_vlcTabType30, 0, 1)];
+ else
+ samples[0] = SB_DITHERING_NOISE(sb, _noiseIdx);
+
+ run = 1;
+ break;
+
+ case 34:
+ if (BITS_LEFT(length,gb) >= 7) {
+ if (type34_first) {
+ type34_div = (float)(1 << getBits(gb, 2));
+ samples[0] = ((float)getBits(gb, 5) - 16.0) / 15.0;
+ type34_predictor = samples[0];
+ type34_first = 0;
+ } else {
+ samples[0] = type34_delta[qdm2_get_vlc(gb, &_vlcTabType34, 0, 1)] / type34_div + type34_predictor;
+ type34_predictor = samples[0];
+ }
+ } else {
+ samples[0] = SB_DITHERING_NOISE(sb, _noiseIdx);
+ }
+ run = 1;
+ break;
+
+ default:
+ samples[0] = SB_DITHERING_NOISE(sb, _noiseIdx);
+ run = 1;
+ break;
+ }
+
+ if (joined_stereo) {
+ float tmp[10][MPA_MAX_CHANNELS];
+
+ for (k = 0; k < run; k++) {
+ tmp[k][0] = samples[k];
+ tmp[k][1] = (sign_bits[(j + k) / 8]) ? -samples[k] : samples[k];
+ }
+ for (chs = 0; chs < _channels; chs++)
+ for (k = 0; k < run; k++)
+ if ((j + k) < 128)
+ _sbSamples[chs][j + k][sb] = (int32)(_toneLevel[chs][sb][((j + k)/2)] * tmp[k][chs] + .5);
+ } else {
+ for (k = 0; k < run; k++)
+ if ((j + k) < 128)
+ _sbSamples[ch][j + k][sb] = (int32)(_toneLevel[ch][sb][(j + k)/2] * samples[k] + .5);
+ }
+
+ j += run;
+ } // j loop
+ } // channel loop
+ } // subband loop
+}
+
+/**
+ * Init the first element of a channel in quantized_coeffs with data from packet 10 (quantized_coeffs[ch][0]).
+ * This is similar to process_subpacket_9, but for a single channel and for element [0]
+ * same VLC tables as process_subpacket_9 are used.
+ *
+ * @param quantized_coeffs pointer to quantized_coeffs[ch][0]
+ * @param gb bitreader context
+ * @param length packet length in bits
+ */
+void QDM2Stream::init_quantized_coeffs_elem0(int8 *quantized_coeffs, GetBitContext *gb, int length) {
+ int i, k, run, level, diff;
+
+ if (BITS_LEFT(length,gb) < 16)
+ return;
+ level = qdm2_get_vlc(gb, &_vlcTabLevel, 0, 2);
+
+ quantized_coeffs[0] = level;
+
+ for (i = 0; i < 7; ) {
+ if (BITS_LEFT(length,gb) < 16)
+ break;
+ run = qdm2_get_vlc(gb, &_vlcTabRun, 0, 1) + 1;
+
+ if (BITS_LEFT(length,gb) < 16)
+ break;
+ diff = qdm2_get_se_vlc(&_vlcTabDiff, gb, 2);
+
+ for (k = 1; k <= run; k++)
+ quantized_coeffs[i + k] = (level + ((k * diff) / run));
+
+ level += diff;
+ i += run;
+ }
+}
+
+/**
+ * Related to synthesis filter, process data from packet 10
+ * Init part of quantized_coeffs via function init_quantized_coeffs_elem0
+ * Init tone_level_idx_hi1, tone_level_idx_hi2, tone_level_idx_mid with data from packet 10
+ *
+ * @param gb bitreader context
+ * @param length packet length in bits
+ */
+void QDM2Stream::init_tone_level_dequantization(GetBitContext *gb, int length) {
+ int sb, j, k, n, ch;
+
+ for (ch = 0; ch < _channels; ch++) {
+ init_quantized_coeffs_elem0(_quantizedCoeffs[ch][0], gb, length);
+
+ if (BITS_LEFT(length,gb) < 16) {
+ memset(_quantizedCoeffs[ch][0], 0, 8);
+ break;
+ }
+ }
+
+ n = _subSampling + 1;
+
+ for (sb = 0; sb < n; sb++)
+ for (ch = 0; ch < _channels; ch++)
+ for (j = 0; j < 8; j++) {
+ if (BITS_LEFT(length,gb) < 1)
+ break;
+ if (getBits1(gb)) {
+ for (k=0; k < 8; k++) {
+ if (BITS_LEFT(length,gb) < 16)
+ break;
+ _toneLevelIdxHi1[ch][sb][j][k] = qdm2_get_vlc(gb, &_vlcTabToneLevelIdxHi1, 0, 2);
+ }
+ } else {
+ for (k=0; k < 8; k++)
+ _toneLevelIdxHi1[ch][sb][j][k] = 0;
+ }
+ }
+
+ n = QDM2_SB_USED(_subSampling) - 4;
+
+ for (sb = 0; sb < n; sb++)
+ for (ch = 0; ch < _channels; ch++) {
+ if (BITS_LEFT(length,gb) < 16)
+ break;
+ _toneLevelIdxHi2[ch][sb] = qdm2_get_vlc(gb, &_vlcTabToneLevelIdxHi2, 0, 2);
+ if (sb > 19)
+ _toneLevelIdxHi2[ch][sb] -= 16;
+ else
+ for (j = 0; j < 8; j++)
+ _toneLevelIdxMid[ch][sb][j] = -16;
+ }
+
+ n = QDM2_SB_USED(_subSampling) - 5;
+
+ for (sb = 0; sb < n; sb++) {
+ for (ch = 0; ch < _channels; ch++) {
+ for (j = 0; j < 8; j++) {
+ if (BITS_LEFT(length,gb) < 16)
+ break;
+ _toneLevelIdxMid[ch][sb][j] = qdm2_get_vlc(gb, &_vlcTabToneLevelIdxMid, 0, 2) - 32;
+ }
+ }
+ }
+}
+
+/**
+ * Process subpacket 9, init quantized_coeffs with data from it
+ *
+ * @param node pointer to node with packet
+ */
+void QDM2Stream::process_subpacket_9(QDM2SubPNode *node) {
+ GetBitContext gb;
+ int i, j, k, n, ch, run, level, diff;
+
+ initGetBits(&gb, node->packet->data, node->packet->size*8);
+
+ n = coeff_per_sb_for_avg[_coeffPerSbSelect][QDM2_SB_USED(_subSampling) - 1] + 1; // same as averagesomething function
+
+ for (i = 1; i < n; i++)
+ for (ch = 0; ch < _channels; ch++) {
+ level = qdm2_get_vlc(&gb, &_vlcTabLevel, 0, 2);
+ _quantizedCoeffs[ch][i][0] = level;
+
+ for (j = 0; j < (8 - 1); ) {
+ run = qdm2_get_vlc(&gb, &_vlcTabRun, 0, 1) + 1;
+ diff = qdm2_get_se_vlc(&_vlcTabDiff, &gb, 2);
+
+ for (k = 1; k <= run; k++)
+ _quantizedCoeffs[ch][i][j + k] = (level + ((k*diff) / run));
+
+ level += diff;
+ j += run;
+ }
+ }
+
+ for (ch = 0; ch < _channels; ch++)
+ for (i = 0; i < 8; i++)
+ _quantizedCoeffs[ch][0][i] = 0;
+}
+
+/**
+ * Process subpacket 10 if not null, else
+ *
+ * @param node pointer to node with packet
+ * @param length packet length in bits
+ */
+void QDM2Stream::process_subpacket_10(QDM2SubPNode *node, int length) {
+ GetBitContext gb;
+
+ initGetBits(&gb, ((node == NULL) ? _emptyBuffer : node->packet->data), ((node == NULL) ? 0 : node->packet->size*8));
+
+ if (length != 0) {
+ init_tone_level_dequantization(&gb, length);
+ fill_tone_level_array(1);
+ } else {
+ fill_tone_level_array(0);
+ }
+}
+
+/**
+ * Process subpacket 11
+ *
+ * @param node pointer to node with packet
+ * @param length packet length in bit
+ */
+void QDM2Stream::process_subpacket_11(QDM2SubPNode *node, int length) {
+ GetBitContext gb;
+
+ initGetBits(&gb, ((node == NULL) ? _emptyBuffer : node->packet->data), ((node == NULL) ? 0 : node->packet->size*8));
+ if (length >= 32) {
+ int c = getBits (&gb, 13);
+
+ if (c > 3)
+ fill_coding_method_array(_toneLevelIdx, _toneLevelIdxTemp, _codingMethod,
+ _channels, 8*c, _superblocktype_2_3, _cmTableSelect);
+ }
+
+ synthfilt_build_sb_samples(&gb, length, 0, 8);
+}
+
+/**
+ * Process subpacket 12
+ *
+ * @param node pointer to node with packet
+ * @param length packet length in bits
+ */
+void QDM2Stream::process_subpacket_12(QDM2SubPNode *node, int length) {
+ GetBitContext gb;
+
+ initGetBits(&gb, ((node == NULL) ? _emptyBuffer : node->packet->data), ((node == NULL) ? 0 : node->packet->size*8));
+ synthfilt_build_sb_samples(&gb, length, 8, QDM2_SB_USED(_subSampling));
+}
+
+/*
+ * Process new subpackets for synthesis filter
+ *
+ * @param list list with synthesis filter packets (list D)
+ */
+void QDM2Stream::process_synthesis_subpackets(QDM2SubPNode *list) {
+ struct QDM2SubPNode *nodes[4];
+
+ nodes[0] = qdm2_search_subpacket_type_in_list(list, 9);
+ if (nodes[0] != NULL)
+ process_subpacket_9(nodes[0]);
+
+ nodes[1] = qdm2_search_subpacket_type_in_list(list, 10);
+ if (nodes[1] != NULL)
+ process_subpacket_10(nodes[1], nodes[1]->packet->size << 3);
+ else
+ process_subpacket_10(NULL, 0);
+
+ nodes[2] = qdm2_search_subpacket_type_in_list(list, 11);
+ if (nodes[0] != NULL && nodes[1] != NULL && nodes[2] != NULL)
+ process_subpacket_11(nodes[2], (nodes[2]->packet->size << 3));
+ else
+ process_subpacket_11(NULL, 0);
+
+ nodes[3] = qdm2_search_subpacket_type_in_list(list, 12);
+ if (nodes[0] != NULL && nodes[1] != NULL && nodes[3] != NULL)
+ process_subpacket_12(nodes[3], (nodes[3]->packet->size << 3));
+ else
+ process_subpacket_12(NULL, 0);
+}
+
+/*
+ * Decode superblock, fill packet lists.
+ *
+ */
+void QDM2Stream::qdm2_decode_super_block(void) {
+ GetBitContext gb;
+ struct QDM2SubPacket header, *packet;
+ int i, packet_bytes, sub_packet_size, subPacketsD;
+ unsigned int next_index = 0;
+
+ memset(_toneLevelIdxHi1, 0, sizeof(_toneLevelIdxHi1));
+ memset(_toneLevelIdxMid, 0, sizeof(_toneLevelIdxMid));
+ memset(_toneLevelIdxHi2, 0, sizeof(_toneLevelIdxHi2));
+
+ _subPacketsB = 0;
+ subPacketsD = 0;
+
+ average_quantized_coeffs(); // average elements in quantized_coeffs[max_ch][10][8]
+
+ initGetBits(&gb, _compressedData, _packetSize*8);
+ qdm2_decode_sub_packet_header(&gb, &header);
+
+ if (header.type < 2 || header.type >= 8) {
+ _hasErrors = true;
+ error("QDM2 : bad superblock type");
+ return;
+ }
+
+ _superblocktype_2_3 = (header.type == 2 || header.type == 3);
+ packet_bytes = (_packetSize - getBitsCount(&gb) / 8);
+
+ initGetBits(&gb, header.data, header.size*8);
+
+ if (header.type == 2 || header.type == 4 || header.type == 5) {
+ int csum = 257 * getBits(&gb, 8) + 2 * getBits(&gb, 8);
+
+ csum = qdm2_packet_checksum(_compressedData, _packetSize, csum);
+
+ if (csum != 0) {
+ _hasErrors = true;
+ error("QDM2 : bad packet checksum");
+ return;
+ }
+ }
+
+ _subPacketListB[0].packet = NULL;
+ _subPacketListD[0].packet = NULL;
+
+ for (i = 0; i < 6; i++)
+ if (--_fftLevelExp[i] < 0)
+ _fftLevelExp[i] = 0;
+
+ for (i = 0; packet_bytes > 0; i++) {
+ int j;
+
+ _subPacketListA[i].next = NULL;
+
+ if (i > 0) {
+ _subPacketListA[i - 1].next = &_subPacketListA[i];
+
+ // seek to next block
+ initGetBits(&gb, header.data, header.size*8);
+ skipBits(&gb, next_index*8);
+
+ if (next_index >= header.size)
+ break;
+ }
+
+ // decode subpacket
+ packet = &_subPackets[i];
+ qdm2_decode_sub_packet_header(&gb, packet);
+ next_index = packet->size + getBitsCount(&gb) / 8;
+ sub_packet_size = ((packet->size > 0xff) ? 1 : 0) + packet->size + 2;
+
+ if (packet->type == 0)
+ break;
+
+ if (sub_packet_size > packet_bytes) {
+ if (packet->type != 10 && packet->type != 11 && packet->type != 12)
+ break;
+ packet->size += packet_bytes - sub_packet_size;
+ }
+
+ packet_bytes -= sub_packet_size;
+
+ // add subpacket to 'all subpackets' list
+ _subPacketListA[i].packet = packet;
+
+ // add subpacket to related list
+ if (packet->type == 8) {
+ error("Unsupported packet type 8");
+ return;
+ } else if (packet->type >= 9 && packet->type <= 12) {
+ // packets for MPEG Audio like Synthesis Filter
+ QDM2_LIST_ADD(_subPacketListD, subPacketsD, packet);
+ } else if (packet->type == 13) {
+ for (j = 0; j < 6; j++)
+ _fftLevelExp[j] = getBits(&gb, 6);
+ } else if (packet->type == 14) {
+ for (j = 0; j < 6; j++)
+ _fftLevelExp[j] = qdm2_get_vlc(&gb, &_fftLevelExpVlc, 0, 2);
+ } else if (packet->type == 15) {
+ error("Unsupported packet type 15");
+ return;
+ } else if (packet->type >= 16 && packet->type < 48 && !fft_subpackets[packet->type - 16]) {
+ // packets for FFT
+ QDM2_LIST_ADD(_subPacketListB, _subPacketsB, packet);
+ }
+ } // Packet bytes loop
+
+// ****************************************************************
+ if (_subPacketListD[0].packet != NULL) {
+ process_synthesis_subpackets(_subPacketListD);
+ _doSynthFilter = 1;
+ } else if (_doSynthFilter) {
+ process_subpacket_10(NULL, 0);
+ process_subpacket_11(NULL, 0);
+ process_subpacket_12(NULL, 0);
+ }
+// ****************************************************************
+}
+
+void QDM2Stream::qdm2_fft_init_coefficient(int sub_packet, int offset, int duration,
+ int channel, int exp, int phase) {
+ if (_fftCoefsMinIndex[duration] < 0)
+ _fftCoefsMinIndex[duration] = _fftCoefsIndex;
+
+ _fftCoefs[_fftCoefsIndex].sub_packet = ((sub_packet >= 16) ? (sub_packet - 16) : sub_packet);
+ _fftCoefs[_fftCoefsIndex].channel = channel;
+ _fftCoefs[_fftCoefsIndex].offset = offset;
+ _fftCoefs[_fftCoefsIndex].exp = exp;
+ _fftCoefs[_fftCoefsIndex].phase = phase;
+ _fftCoefsIndex++;
+}
+
+void QDM2Stream::qdm2_fft_decode_tones(int duration, GetBitContext *gb, int b) {
+ debug(1, "QDM2Stream::qdm2_fft_decode_tones() duration: %d b:%d", duration, b);
+ int channel, stereo, phase, exp;
+ int local_int_4, local_int_8, stereo_phase, local_int_10;
+ int local_int_14, stereo_exp, local_int_20, local_int_28;
+ int n, offset;
+
+ local_int_4 = 0;
+ local_int_28 = 0;
+ local_int_20 = 2;
+ local_int_8 = (4 - duration);
+ local_int_10 = 1 << (_groupOrder - duration - 1);
+ offset = 1;
+
+ while (1) {
+ if (_superblocktype_2_3) {
+ debug(1, "QDM2Stream::qdm2_fft_decode_tones() local_int_8: %d", local_int_8);
+ while ((n = qdm2_get_vlc(gb, &_vlcTabFftToneOffset[local_int_8], 1, 2)) < 2) {
+ debug(1, "QDM2Stream::qdm2_fft_decode_tones() local_int_8: %d", local_int_8);
+ offset = 1;
+ if (n == 0) {
+ local_int_4 += local_int_10;
+ local_int_28 += (1 << local_int_8);
+ } else {
+ local_int_4 += 8*local_int_10;
+ local_int_28 += (8 << local_int_8);
+ }
+ }
+ offset += (n - 2);
+ } else {
+ offset += qdm2_get_vlc(gb, &_vlcTabFftToneOffset[local_int_8], 1, 2);
+ while (offset >= (local_int_10 - 1)) {
+ offset += (1 - (local_int_10 - 1));
+ local_int_4 += local_int_10;
+ local_int_28 += (1 << local_int_8);
+ }
+ }
+
+ if (local_int_4 >= _blockSize)
+ return;
+
+ local_int_14 = (offset >> local_int_8);
+
+ if (_channels > 1) {
+ channel = getBits1(gb);
+ stereo = getBits1(gb);
+ } else {
+ channel = 0;
+ stereo = 0;
+ }
+
+ exp = qdm2_get_vlc(gb, (b ? &_fftLevelExpVlc : &_fftLevelExpAltVlc), 0, 2);
+ exp += _fftLevelExp[fft_level_index_table[local_int_14]];
+ exp = (exp < 0) ? 0 : exp;
+
+ phase = getBits(gb, 3);
+ stereo_exp = 0;
+ stereo_phase = 0;
+
+ if (stereo) {
+ stereo_exp = (exp - qdm2_get_vlc(gb, &_fftStereoExpVlc, 0, 1));
+ stereo_phase = (phase - qdm2_get_vlc(gb, &_fftStereoPhaseVlc, 0, 1));
+ if (stereo_phase < 0)
+ stereo_phase += 8;
+ }
+
+ if (_frequencyRange > (local_int_14 + 1)) {
+ int sub_packet = (local_int_20 + local_int_28);
+
+ qdm2_fft_init_coefficient(sub_packet, offset, duration, channel, exp, phase);
+ if (stereo)
+ qdm2_fft_init_coefficient(sub_packet, offset, duration, (1 - channel), stereo_exp, stereo_phase);
+ }
+
+ offset++;
+ }
+}
+
+void QDM2Stream::qdm2_decode_fft_packets(void) {
+ debug(1, "QDM2Stream::qdm2_decode_fft_packets()");
+ int i, j, min, max, value, type, unknown_flag;
+ GetBitContext gb;
+
+ if (_subPacketListB[0].packet == NULL)
+ return;
+
+ // reset minimum indexes for FFT coefficients
+ _fftCoefsIndex = 0;
+ for (i=0; i < 5; i++)
+ _fftCoefsMinIndex[i] = -1;
+
+ // process subpackets ordered by type, largest type first
+ for (i = 0, max = 256; i < _subPacketsB; i++) {
+ QDM2SubPacket *packet= NULL;
+
+ // find subpacket with largest type less than max
+ for (j = 0, min = 0; j < _subPacketsB; j++) {
+ value = _subPacketListB[j].packet->type;
+ if (value > min && value < max) {
+ min = value;
+ packet = _subPacketListB[j].packet;
+ }
+ }
+
+ max = min;
+
+ // check for errors (?)
+ if (!packet)
+ return;
+
+ if (i == 0 && (packet->type < 16 || packet->type >= 48 || fft_subpackets[packet->type - 16]))
+ return;
+
+ // decode FFT tones
+ debug(1, "QDM2Stream::qdm2_decode_fft_packets initGetBits() packet->size*8: %d", packet->size*8);
+ initGetBits(&gb, packet->data, packet->size*8);
+
+ if (packet->type >= 32 && packet->type < 48 && !fft_subpackets[packet->type - 16])
+ unknown_flag = 1;
+ else
+ unknown_flag = 0;
+
+ type = packet->type;
+
+ if ((type >= 17 && type < 24) || (type >= 33 && type < 40)) {
+ int duration = _subSampling + 5 - (type & 15);
+
+ if (duration >= 0 && duration < 4) { // TODO: Should be <= 4?
+ debug(1, "QDM2Stream::qdm2_decode_fft_packets qdm2_fft_decode_tones() #1");
+ qdm2_fft_decode_tones(duration, &gb, unknown_flag);
+ }
+ } else if (type == 31) {
+ for (j=0; j < 4; j++) {
+ debug(1, "QDM2Stream::qdm2_decode_fft_packets qdm2_fft_decode_tones() #2");
+ qdm2_fft_decode_tones(j, &gb, unknown_flag);
+ }
+ } else if (type == 46) {
+ for (j=0; j < 6; j++)
+ _fftLevelExp[j] = getBits(&gb, 6);
+ for (j=0; j < 4; j++) {
+ debug(1, "QDM2Stream::qdm2_decode_fft_packets qdm2_fft_decode_tones() #3");
+ qdm2_fft_decode_tones(j, &gb, unknown_flag);
+ }
+ }
+ } // Loop on B packets
+
+ // calculate maximum indexes for FFT coefficients
+ for (i = 0, j = -1; i < 5; i++)
+ if (_fftCoefsMinIndex[i] >= 0) {
+ if (j >= 0)
+ _fftCoefsMaxIndex[j] = _fftCoefsMinIndex[i];
+ j = i;
+ }
+ if (j >= 0)
+ _fftCoefsMaxIndex[j] = _fftCoefsIndex;
+}
+
+void QDM2Stream::qdm2_fft_generate_tone(FFTTone *tone)
+{
+ float level, f[6];
+ int i;
+ QDM2Complex c;
+ const double iscale = 2.0 * PI / 512.0;
+
+ tone->phase += tone->phase_shift;
+
+ // calculate current level (maximum amplitude) of tone
+ level = fft_tone_envelope_table[tone->duration][tone->time_index] * tone->level;
+ c.im = level * sin(tone->phase*iscale);
+ c.re = level * cos(tone->phase*iscale);
+
+ // generate FFT coefficients for tone
+ if (tone->duration >= 3 || tone->cutoff >= 3) {
+ tone->complex[0].im += c.im;
+ tone->complex[0].re += c.re;
+ tone->complex[1].im -= c.im;
+ tone->complex[1].re -= c.re;
+ } else {
+ f[1] = -tone->table[4];
+ f[0] = tone->table[3] - tone->table[0];
+ f[2] = 1.0 - tone->table[2] - tone->table[3];
+ f[3] = tone->table[1] + tone->table[4] - 1.0;
+ f[4] = tone->table[0] - tone->table[1];
+ f[5] = tone->table[2];
+ for (i = 0; i < 2; i++) {
+ tone->complex[fft_cutoff_index_table[tone->cutoff][i]].re += c.re * f[i];
+ tone->complex[fft_cutoff_index_table[tone->cutoff][i]].im += c.im *((tone->cutoff <= i) ? -f[i] : f[i]);
+ }
+ for (i = 0; i < 4; i++) {
+ tone->complex[i].re += c.re * f[i+2];
+ tone->complex[i].im += c.im * f[i+2];
+ }
+ }
+
+ // copy the tone if it has not yet died out
+ if (++tone->time_index < ((1 << (5 - tone->duration)) - 1)) {
+ memcpy(&_fftTones[_fftToneEnd], tone, sizeof(FFTTone));
+ _fftToneEnd = (_fftToneEnd + 1) % 1000;
+ }
+}
+
+void QDM2Stream::qdm2_fft_tone_synthesizer(uint8 sub_packet) {
+ int i, j, ch;
+ const double iscale = 0.25 * PI;
+
+ for (ch = 0; ch < _channels; ch++) {
+ memset(_fft.complex[ch], 0, _frameSize * sizeof(QDM2Complex));
+ }
+
+ // apply FFT tones with duration 4 (1 FFT period)
+ if (_fftCoefsMinIndex[4] >= 0)
+ for (i = _fftCoefsMinIndex[4]; i < _fftCoefsMaxIndex[4]; i++) {
+ float level;
+ QDM2Complex c;
+
+ if (_fftCoefs[i].sub_packet != sub_packet)
+ break;
+
+ ch = (_channels == 1) ? 0 : _fftCoefs[i].channel;
+ level = (_fftCoefs[i].exp < 0) ? 0.0 : fft_tone_level_table[_superblocktype_2_3 ? 0 : 1][_fftCoefs[i].exp & 63];
+
+ c.re = level * cos(_fftCoefs[i].phase * iscale);
+ c.im = level * sin(_fftCoefs[i].phase * iscale);
+ _fft.complex[ch][_fftCoefs[i].offset + 0].re += c.re;
+ _fft.complex[ch][_fftCoefs[i].offset + 0].im += c.im;
+ _fft.complex[ch][_fftCoefs[i].offset + 1].re -= c.re;
+ _fft.complex[ch][_fftCoefs[i].offset + 1].im -= c.im;
+ }
+
+ // generate existing FFT tones
+ for (i = _fftToneEnd; i != _fftToneStart; ) {
+ qdm2_fft_generate_tone(&_fftTones[_fftToneStart]);
+ _fftToneStart = (_fftToneStart + 1) % 1000;
+ }
+
+ // create and generate new FFT tones with duration 0 (long) to 3 (short)
+ for (i = 0; i < 4; i++)
+ if (_fftCoefsMinIndex[i] >= 0) {
+ for (j = _fftCoefsMinIndex[i]; j < _fftCoefsMaxIndex[i]; j++) {
+ int offset, four_i;
+ FFTTone tone;
+
+ if (_fftCoefs[j].sub_packet != sub_packet)
+ break;
+
+ four_i = (4 - i);
+ offset = _fftCoefs[j].offset >> four_i;
+ ch = (_channels == 1) ? 0 : _fftCoefs[j].channel;
+
+ if (offset < _frequencyRange) {
+ if (offset < 2)
+ tone.cutoff = offset;
+ else
+ tone.cutoff = (offset >= 60) ? 3 : 2;
+
+ tone.level = (_fftCoefs[j].exp < 0) ? 0.0 : fft_tone_level_table[_superblocktype_2_3 ? 0 : 1][_fftCoefs[j].exp & 63];
+ tone.complex = &_fft.complex[ch][offset];
+ tone.table = fft_tone_sample_table[i][_fftCoefs[j].offset - (offset << four_i)];
+ tone.phase = 64 * _fftCoefs[j].phase - (offset << 8) - 128;
+ tone.phase_shift = (2 * _fftCoefs[j].offset + 1) << (7 - four_i);
+ tone.duration = i;
+ tone.time_index = 0;
+
+ qdm2_fft_generate_tone(&tone);
+ }
+ }
+ _fftCoefsMinIndex[i] = j;
+ }
+}
+
+void QDM2Stream::qdm2_calculate_fft(int channel) {
+ debug(1, "QDM2Stream::qdm2_calculate_fft channel: %d", channel);
+ const float gain = (_channels == 1 && _channels == 2) ? 0.5f : 1.0f;
+ int i;
+
+ _fft.complex[channel][0].re *= 2.0f;
+ _fft.complex[channel][0].im = 0.0f;
+
+ //debug(1, "QDM2Stream::qdm2_calculate_fft _fft.complex[channel][0].re: %lf", _fft.complex[channel][0].re);
+ //debug(1, "QDM2Stream::qdm2_calculate_fft _fft.complex[channel][0].im: %lf", _fft.complex[channel][0].im);
+
+ rdftCalc(&_rdftCtx, (float *)_fft.complex[channel]);
+
+ // add samples to output buffer
+ for (i = 0; i < ((_fftFrameSize + 15) & ~15); i++)
+ _outputBuffer[_channels * i + channel] += ((float *) _fft.complex[channel])[i] * gain;
+}
+
+/**
+ * @param index subpacket number
+ */
+void QDM2Stream::qdm2_synthesis_filter(uint8 index)
+{
+ int16 samples[MPA_MAX_CHANNELS * MPA_FRAME_SIZE];
+ int i, k, ch, sb_used, sub_sampling, dither_state = 0;
+
+ // copy sb_samples
+ sb_used = QDM2_SB_USED(_subSampling);
+
+ for (ch = 0; ch < _channels; ch++)
+ for (i = 0; i < 8; i++)
+ for (k = sb_used; k < 32; k++)
+ _sbSamples[ch][(8 * index) + i][k] = 0;
+
+ for (ch = 0; ch < _channels; ch++) {
+ int16 *samples_ptr = samples + ch;
+
+ for (i = 0; i < 8; i++) {
+ ff_mpa_synth_filter(_synthBuf[ch], &(_synthBufOffset[ch]),
+ ff_mpa_synth_window, &dither_state,
+ samples_ptr, _channels,
+ _sbSamples[ch][(8 * index) + i]);
+ samples_ptr += 32 * _channels;
+ }
+ }
+
+ // add samples to output buffer
+ sub_sampling = (4 >> _subSampling);
+
+ for (ch = 0; ch < _channels; ch++)
+ for (i = 0; i < _sFrameSize; i++)
+ _outputBuffer[_channels * i + ch] += (float)(samples[_channels * sub_sampling * i + ch] >> (sizeof(int16)*8-16));
+}
+
+int QDM2Stream::qdm2_decodeFrame(Common::SeekableReadStream *in) {
+ debug(1, "QDM2Stream::qdm2_decodeFrame in->pos(): %d in->size(): %d", in->pos(), in->size());
+ int ch, i;
+ const int frame_size = (_sFrameSize * _channels);
+
+ // select input buffer
+ if(in->eos() || in->size() == in->pos()) {
+ debug(1, "QDM2Stream::qdm2_decodeFrame End of Input Stream");
+ return 0;
+ }
+ if((in->size() - in->pos()) < _packetSize) {
+ debug(1, "QDM2Stream::qdm2_decodeFrame Insufficient Packet Data in Input Stream Found: %d Need: %d", in->size() - in->pos(), _packetSize);
+ return 0;
+ }
+
+ for(i = 0; i < _packetSize; i++)
+ _compressedData[i] = in->readByte();
+ debug(1, "QDM2Stream::qdm2_decodeFrame constructed input data");
+
+ // copy old block, clear new block of output samples
+ memmove(_outputBuffer, &_outputBuffer[frame_size], frame_size * sizeof(float));
+ memset(&_outputBuffer[frame_size], 0, frame_size * sizeof(float));
+ debug(1, "QDM2Stream::qdm2_decodeFrame cleared outputBuffer");
+
+ // decode block of QDM2 compressed data
+ debug(1, "QDM2Stream::qdm2_decodeFrame decode block of QDM2 compressed data");
+ if (_subPacket == 0) {
+ _hasErrors = false; // reset it for a new super block
+ debug(1, "QDM2 : Superblock follows");
+ qdm2_decode_super_block();
+ }
+
+ // parse subpackets
+ debug(1, "QDM2Stream::qdm2_decodeFrame parse subpackets");
+ if (!_hasErrors) {
+ if (_subPacket == 2) {
+ debug(1, "QDM2Stream::qdm2_decodeFrame qdm2_decode_fft_packets()");
+ qdm2_decode_fft_packets();
+ }
+
+ debug(1, "QDM2Stream::qdm2_decodeFrame qdm2_fft_tone_synthesizer(%d)", _subPacket);
+ qdm2_fft_tone_synthesizer(_subPacket);
+ }
+
+ // sound synthesis stage 1 (FFT)
+ debug(1, "QDM2Stream::qdm2_decodeFrame sound synthesis stage 1 (FFT)");
+ for (ch = 0; ch < _channels; ch++) {
+ qdm2_calculate_fft(ch);
+
+ if (!_hasErrors && _subPacketListC[0].packet != NULL) {
+ error("QDM2 : has errors, and C list is not empty");
+ return 0;
+ }
+ }
+
+ // sound synthesis stage 2 (MPEG audio like synthesis filter)
+ debug(1, "QDM2Stream::qdm2_decodeFrame sound synthesis stage 2 (MPEG audio like synthesis filter)");
+ if (!_hasErrors && _doSynthFilter)
+ qdm2_synthesis_filter(_subPacket);
+
+ _subPacket = (_subPacket + 1) % 16;
+
+ if(_hasErrors)
+ warning("QDM2 Packet error...");
+
+ // clip and convert output float[] to 16bit signed samples
+ debug(1, "QDM2Stream::qdm2_decodeFrame clip and convert output float[] to 16bit signed samples");
+
+/*
+ debugN(1, "Input Data Packet:");
+ for(i = 0; i < _packetSize; i++) {
+ debugN(1, " %d", _compressedData[i]);
+ }
+ debugN(1, " Output Data Packet:");
+ for(i = 0; i < frame_size; i++) {
+ debugN(1, " %d", (int)_outputBuffer[i]);
+ }
+ debug(1, "");
+*/
+
+ for (i = 0; i < frame_size; i++) {
+ //debug(1, "QDM2Stream::qdm2_decodeFrame i: %d", i);
+ int value = (int)_outputBuffer[i];
+
+ if (value > SOFTCLIP_THRESHOLD)
+ value = (value > HARDCLIP_THRESHOLD) ? 32767 : _softclipTable[ value - SOFTCLIP_THRESHOLD];
+ else if (value < -SOFTCLIP_THRESHOLD)
+ value = (value < -HARDCLIP_THRESHOLD) ? -32767 : -_softclipTable[-value - SOFTCLIP_THRESHOLD];
+
+ _outputSamples.push_back(value);
+ }
+ return frame_size;
+}
+
+int QDM2Stream::readBuffer(int16 *buffer, const int numSamples) {
+ debug(1, "QDM2Stream::readBuffer numSamples: %d", numSamples);
+ int32 decodedSamples = _outputSamples.size();
+ int32 i;
+
+ //while((int)_outputSamples.size() < numSamples) {
+ while(!_stream->eos() && _stream->pos() != _stream->size()) {
+ i = qdm2_decodeFrame(_stream);
+ if(i == 0)
+ break; // Out Of Decode Frames...
+ decodedSamples += i;
+ }
+ if(decodedSamples > numSamples)
+ decodedSamples = numSamples;
+
+ for(i = 0; i < decodedSamples; i++)
+ buffer[i] = _outputSamples.remove_at(0);
+
+ return decodedSamples;
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/video/qdm2.h b/engines/mohawk/video/qdm2.h
new file mode 100644
index 0000000000..1a08064b0b
--- /dev/null
+++ b/engines/mohawk/video/qdm2.h
@@ -0,0 +1,288 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MOHAWK_VIDEO_QDM2_H
+#define MOHAWK_VIDEO_QDM2_H
+
+#include "sound/audiostream.h"
+#include "common/stream.h"
+
+namespace Mohawk {
+
+enum {
+ SOFTCLIP_THRESHOLD = 27600,
+ HARDCLIP_THRESHOLD = 35716,
+ MPA_MAX_CHANNELS = 2,
+ MPA_FRAME_SIZE = 1152,
+ FF_INPUT_BUFFER_PADDING_SIZE = 8
+};
+
+typedef int8 sb_int8_array[2][30][64];
+
+/* bit input */
+/* buffer, buffer_end and size_in_bits must be present and used by every reader */
+struct GetBitContext {
+ const uint8 *buffer, *bufferEnd;
+ int index;
+ int sizeInBits;
+};
+
+struct QDM2SubPacket {
+ int type;
+ unsigned int size;
+ const uint8 *data; // pointer to subpacket data (points to input data buffer, it's not a private copy)
+};
+
+struct QDM2SubPNode {
+ QDM2SubPacket *packet;
+ struct QDM2SubPNode *next; // pointer to next packet in the list, NULL if leaf node
+};
+
+struct QDM2Complex {
+ float re;
+ float im;
+};
+
+struct FFTTone {
+ float level;
+ QDM2Complex *complex;
+ const float *table;
+ int phase;
+ int phase_shift;
+ int duration;
+ short time_index;
+ short cutoff;
+};
+
+struct FFTCoefficient {
+ int16 sub_packet;
+ uint8 channel;
+ int16 offset;
+ int16 exp;
+ uint8 phase;
+};
+
+struct VLC {
+ int32 bits;
+ int16 (*table)[2]; // code, bits
+ int32 table_size;
+ int32 table_allocated;
+};
+
+#include "common/pack-start.h"
+struct QDM2FFT {
+ QDM2Complex complex[MPA_MAX_CHANNELS][256];
+} PACKED_STRUCT;
+#include "common/pack-end.h"
+
+enum RDFTransformType {
+ RDFT,
+ IRDFT,
+ RIDFT,
+ IRIDFT
+};
+
+struct FFTComplex {
+ float re, im;
+};
+
+struct FFTContext {
+ int nbits;
+ int inverse;
+ uint16 *revtab;
+ FFTComplex *exptab;
+ FFTComplex *tmpBuf;
+ int mdctSize; // size of MDCT (i.e. number of input data * 2)
+ int mdctBits; // n = 2^nbits
+ // pre/post rotation tables
+ float *tcos;
+ float *tsin;
+ void (*fftPermute)(struct FFTContext *s, FFTComplex *z);
+ void (*fftCalc)(struct FFTContext *s, FFTComplex *z);
+ void (*imdctCalc)(struct FFTContext *s, float *output, const float *input);
+ void (*imdctHalf)(struct FFTContext *s, float *output, const float *input);
+ void (*mdctCalc)(struct FFTContext *s, float *output, const float *input);
+ int splitRadix;
+ int permutation;
+};
+
+enum {
+ FF_MDCT_PERM_NONE = 0,
+ FF_MDCT_PERM_INTERLEAVE = 1
+};
+
+struct RDFTContext {
+ int nbits;
+ int inverse;
+ int signConvention;
+
+ // pre/post rotation tables
+ float *tcos;
+ float *tsin;
+ FFTContext fft;
+};
+
+class QDM2Stream : public Audio::AudioStream {
+public:
+ QDM2Stream(Common::SeekableReadStream *stream, Common::SeekableReadStream *extraData);
+ ~QDM2Stream();
+
+ bool isStereo() const { return _channels == 2; }
+ bool endOfData() const { return ((_stream->pos() == _stream->size()) && (_outputSamples.size() == 0)); }
+ int getRate() const { return _sampleRate; }
+ int readBuffer(int16 *buffer, const int numSamples);
+
+private:
+ Common::SeekableReadStream *_stream;
+
+ // Parameters from codec header, do not change during playback
+ uint8 _channels;
+ uint16 _sampleRate;
+ uint16 _bitRate;
+ uint16 _blockSize; // Group
+ uint16 _frameSize; // FFT
+ uint16 _packetSize; // Checksum
+
+ // Parameters built from header parameters, do not change during playback
+ int _groupOrder; // order of frame group
+ int _fftOrder; // order of FFT (actually fft order+1)
+ int _fftFrameSize; // size of fft frame, in components (1 comples = re + im)
+ int _sFrameSize; // size of data frame
+ int _frequencyRange;
+ int _subSampling; // subsampling: 0=25%, 1=50%, 2=100% */
+ int _coeffPerSbSelect; // selector for "num. of coeffs. per subband" tables. Can be 0, 1, 2
+ int _cmTableSelect; // selector for "coding method" tables. Can be 0, 1 (from init: 0-4)
+
+ // Packets and packet lists
+ QDM2SubPacket _subPackets[16]; // the packets themselves
+ QDM2SubPNode _subPacketListA[16]; // list of all packets
+ QDM2SubPNode _subPacketListB[16]; // FFT packets B are on list
+ int _subPacketsB; // number of packets on 'B' list
+ QDM2SubPNode _subPacketListC[16]; // packets with errors?
+ QDM2SubPNode _subPacketListD[16]; // DCT packets
+
+ // FFT and tones
+ FFTTone _fftTones[1000];
+ int _fftToneStart;
+ int _fftToneEnd;
+ FFTCoefficient _fftCoefs[1000];
+ int _fftCoefsIndex;
+ int _fftCoefsMinIndex[5];
+ int _fftCoefsMaxIndex[5];
+ int _fftLevelExp[6];
+ //RDFTContext _rdftCtx;
+ QDM2FFT _fft;
+
+ // I/O data
+ uint8 *_compressedData;
+ float _outputBuffer[1024];
+ Common::Array<int16> _outputSamples;
+
+ // Synthesis filter
+ int16 ff_mpa_synth_window[512];
+ int16 _synthBuf[MPA_MAX_CHANNELS][512*2];
+ int _synthBufOffset[MPA_MAX_CHANNELS];
+ int32 _sbSamples[MPA_MAX_CHANNELS][128][32];
+
+ // Mixed temporary data used in decoding
+ float _toneLevel[MPA_MAX_CHANNELS][30][64];
+ int8 _codingMethod[MPA_MAX_CHANNELS][30][64];
+ int8 _quantizedCoeffs[MPA_MAX_CHANNELS][10][8];
+ int8 _toneLevelIdxBase[MPA_MAX_CHANNELS][30][8];
+ int8 _toneLevelIdxHi1[MPA_MAX_CHANNELS][3][8][8];
+ int8 _toneLevelIdxMid[MPA_MAX_CHANNELS][26][8];
+ int8 _toneLevelIdxHi2[MPA_MAX_CHANNELS][26];
+ int8 _toneLevelIdx[MPA_MAX_CHANNELS][30][64];
+ int8 _toneLevelIdxTemp[MPA_MAX_CHANNELS][30][64];
+
+ // Flags
+ bool _hasErrors; // packet has errors
+ int _superblocktype_2_3; // select fft tables and some algorithm based on superblock type
+ int _doSynthFilter; // used to perform or skip synthesis filter
+
+ uint8 _subPacket; // 0 to 15
+ int _noiseIdx; // index for dithering noise table
+
+ byte _emptyBuffer[FF_INPUT_BUFFER_PADDING_SIZE];
+
+ VLC _vlcTabLevel;
+ VLC _vlcTabDiff;
+ VLC _vlcTabRun;
+ VLC _fftLevelExpAltVlc;
+ VLC _fftLevelExpVlc;
+ VLC _fftStereoExpVlc;
+ VLC _fftStereoPhaseVlc;
+ VLC _vlcTabToneLevelIdxHi1;
+ VLC _vlcTabToneLevelIdxMid;
+ VLC _vlcTabToneLevelIdxHi2;
+ VLC _vlcTabType30;
+ VLC _vlcTabType34;
+ VLC _vlcTabFftToneOffset[5];
+ bool _vlcsInitialized;
+ void initVlc(void);
+
+ uint16 _softclipTable[HARDCLIP_THRESHOLD - SOFTCLIP_THRESHOLD + 1];
+ void softclipTableInit(void);
+
+ float _noiseTable[4096];
+ byte _randomDequantIndex[256][5];
+ byte _randomDequantType24[128][3];
+ void rndTableInit(void);
+
+ float _noiseSamples[128];
+ void initNoiseSamples(void);
+
+ RDFTContext _rdftCtx;
+
+ void average_quantized_coeffs(void);
+ void build_sb_samples_from_noise(int sb);
+ void fix_coding_method_array(int sb, int channels, sb_int8_array coding_method);
+ void fill_tone_level_array(int flag);
+ void fill_coding_method_array(sb_int8_array tone_level_idx, sb_int8_array tone_level_idx_temp,
+ sb_int8_array coding_method, int nb_channels,
+ int c, int superblocktype_2_3, int cm_table_select);
+ void synthfilt_build_sb_samples(GetBitContext *gb, int length, int sb_min, int sb_max);
+ void init_quantized_coeffs_elem0(int8 *quantized_coeffs, GetBitContext *gb, int length);
+ void init_tone_level_dequantization(GetBitContext *gb, int length);
+ void process_subpacket_9(QDM2SubPNode *node);
+ void process_subpacket_10(QDM2SubPNode *node, int length);
+ void process_subpacket_11(QDM2SubPNode *node, int length);
+ void process_subpacket_12(QDM2SubPNode *node, int length);
+ void process_synthesis_subpackets(QDM2SubPNode *list);
+ void qdm2_decode_super_block(void);
+ void qdm2_fft_init_coefficient(int sub_packet, int offset, int duration,
+ int channel, int exp, int phase);
+ void qdm2_fft_decode_tones(int duration, GetBitContext *gb, int b);
+ void qdm2_decode_fft_packets(void);
+ void qdm2_fft_generate_tone(FFTTone *tone);
+ void qdm2_fft_tone_synthesizer(uint8 sub_packet);
+ void qdm2_calculate_fft(int channel);
+ void qdm2_synthesis_filter(uint8 index);
+ int qdm2_decodeFrame(Common::SeekableReadStream *in);
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/video/qdm2data.h b/engines/mohawk/video/qdm2data.h
new file mode 100644
index 0000000000..13fc13f3c1
--- /dev/null
+++ b/engines/mohawk/video/qdm2data.h
@@ -0,0 +1,531 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MOHAWK_VIDEO_QDM2DATA_H
+#define MOHAWK_VIDEO_QDM2DATA_H
+
+#include "common/scummsys.h"
+
+namespace Mohawk {
+
+/// VLC TABLES
+
+// values in this table range from -1..23; adjust retrieved value by -1
+static const uint16 vlc_tab_level_huffcodes[24] = {
+ 0x037c, 0x0004, 0x003c, 0x004c, 0x003a, 0x002c, 0x001c, 0x001a,
+ 0x0024, 0x0014, 0x0001, 0x0002, 0x0000, 0x0003, 0x0007, 0x0005,
+ 0x0006, 0x0008, 0x0009, 0x000a, 0x000c, 0x00fc, 0x007c, 0x017c
+};
+
+static const byte vlc_tab_level_huffbits[24] = {
+ 10, 6, 7, 7, 6, 6, 6, 6, 6, 5, 4, 4, 4, 3, 3, 3, 3, 4, 4, 5, 7, 8, 9, 10
+};
+
+// values in this table range from -1..36; adjust retrieved value by -1
+static const uint16 vlc_tab_diff_huffcodes[37] = {
+ 0x1c57, 0x0004, 0x0000, 0x0001, 0x0003, 0x0002, 0x000f, 0x000e,
+ 0x0007, 0x0016, 0x0037, 0x0027, 0x0026, 0x0066, 0x0006, 0x0097,
+ 0x0046, 0x01c6, 0x0017, 0x0786, 0x0086, 0x0257, 0x00d7, 0x0357,
+ 0x00c6, 0x0386, 0x0186, 0x0000, 0x0157, 0x0c57, 0x0057, 0x0000,
+ 0x0b86, 0x0000, 0x1457, 0x0000, 0x0457
+};
+
+static const byte vlc_tab_diff_huffbits[37] = {
+ 13, 3, 3, 2, 3, 3, 4, 4, 6, 5, 6, 6, 7, 7, 8, 8,
+ 8, 9, 8, 11, 9, 10, 8, 10, 9, 12, 10, 0, 10, 13, 11, 0,
+ 12, 0, 13, 0, 13
+};
+
+// values in this table range from -1..5; adjust retrieved value by -1
+static const byte vlc_tab_run_huffcodes[6] = {
+ 0x1f, 0x00, 0x01, 0x03, 0x07, 0x0f
+};
+
+static const byte vlc_tab_run_huffbits[6] = {
+ 5, 1, 2, 3, 4, 5
+};
+
+// values in this table range from -1..19; adjust retrieved value by -1
+static const uint16 vlc_tab_tone_level_idx_hi1_huffcodes[20] = {
+ 0x5714, 0x000c, 0x0002, 0x0001, 0x0000, 0x0004, 0x0034, 0x0054,
+ 0x0094, 0x0014, 0x0114, 0x0214, 0x0314, 0x0614, 0x0e14, 0x0f14,
+ 0x2714, 0x0714, 0x1714, 0x3714
+};
+
+static const byte vlc_tab_tone_level_idx_hi1_huffbits[20] = {
+ 15, 4, 2, 1, 3, 5, 6, 7, 8, 10, 10, 11, 11, 12, 12, 12, 14, 14, 15, 14
+};
+
+// values in this table range from -1..23; adjust retrieved value by -1
+static const uint16 vlc_tab_tone_level_idx_mid_huffcodes[24] = {
+ 0x0fea, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x03ea, 0x00ea, 0x002a, 0x001a,
+ 0x0006, 0x0001, 0x0000, 0x0002, 0x000a, 0x006a, 0x01ea, 0x07ea
+};
+
+static const byte vlc_tab_tone_level_idx_mid_huffbits[24] = {
+ 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 7, 5, 3, 1, 2, 4, 6, 8, 10, 12
+};
+
+// values in this table range from -1..23; adjust retrieved value by -1
+static const uint16 vlc_tab_tone_level_idx_hi2_huffcodes[24] = {
+ 0x0664, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0064, 0x00e4,
+ 0x00a4, 0x0068, 0x0004, 0x0008, 0x0014, 0x0018, 0x0000, 0x0001,
+ 0x0002, 0x0003, 0x000c, 0x0028, 0x0024, 0x0164, 0x0000, 0x0264
+};
+
+static const byte vlc_tab_tone_level_idx_hi2_huffbits[24] = {
+ 11, 0, 0, 0, 0, 0, 10, 8, 8, 7, 6, 6, 5, 5, 4, 2, 2, 2, 4, 7, 8, 9, 0, 11
+};
+
+// values in this table range from -1..8; adjust retrieved value by -1
+static const byte vlc_tab_type30_huffcodes[9] = {
+ 0x3c, 0x06, 0x00, 0x01, 0x03, 0x02, 0x04, 0x0c, 0x1c
+};
+
+static const byte vlc_tab_type30_huffbits[9] = {
+ 6, 3, 3, 2, 2, 3, 4, 5, 6
+};
+
+// values in this table range from -1..9; adjust retrieved value by -1
+static const byte vlc_tab_type34_huffcodes[10] = {
+ 0x18, 0x00, 0x01, 0x04, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08
+};
+
+static const byte vlc_tab_type34_huffbits[10] = {
+ 5, 4, 3, 3, 3, 3, 3, 3, 3, 5
+};
+
+// values in this table range from -1..22; adjust retrieved value by -1
+static const uint16 vlc_tab_fft_tone_offset_0_huffcodes[23] = {
+ 0x038e, 0x0001, 0x0000, 0x0022, 0x000a, 0x0006, 0x0012, 0x0002,
+ 0x001e, 0x003e, 0x0056, 0x0016, 0x000e, 0x0032, 0x0072, 0x0042,
+ 0x008e, 0x004e, 0x00f2, 0x002e, 0x0036, 0x00c2, 0x018e
+};
+
+static const byte vlc_tab_fft_tone_offset_0_huffbits[23] = {
+ 10, 1, 2, 6, 4, 5, 6, 7, 6, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 6, 6, 8, 10
+};
+
+// values in this table range from -1..27; adjust retrieved value by -1
+static const uint16 vlc_tab_fft_tone_offset_1_huffcodes[28] = {
+ 0x07a4, 0x0001, 0x0020, 0x0012, 0x001c, 0x0008, 0x0006, 0x0010,
+ 0x0000, 0x0014, 0x0004, 0x0032, 0x0070, 0x000c, 0x0002, 0x003a,
+ 0x001a, 0x002c, 0x002a, 0x0022, 0x0024, 0x000a, 0x0064, 0x0030,
+ 0x0062, 0x00a4, 0x01a4, 0x03a4
+};
+
+static const byte vlc_tab_fft_tone_offset_1_huffbits[28] = {
+ 11, 1, 6, 6, 5, 4, 3, 6, 6, 5, 6, 6, 7, 6, 6, 6,
+ 6, 6, 6, 7, 8, 6, 7, 7, 7, 9, 10, 11
+};
+
+// values in this table range from -1..31; adjust retrieved value by -1
+static const uint16 vlc_tab_fft_tone_offset_2_huffcodes[32] = {
+ 0x1760, 0x0001, 0x0000, 0x0082, 0x000c, 0x0006, 0x0003, 0x0007,
+ 0x0008, 0x0004, 0x0010, 0x0012, 0x0022, 0x001a, 0x0000, 0x0020,
+ 0x000a, 0x0040, 0x004a, 0x006a, 0x002a, 0x0042, 0x0002, 0x0060,
+ 0x00aa, 0x00e0, 0x00c2, 0x01c2, 0x0160, 0x0360, 0x0760, 0x0f60
+};
+
+static const byte vlc_tab_fft_tone_offset_2_huffbits[32] = {
+ 13, 2, 0, 8, 4, 3, 3, 3, 4, 4, 5, 5, 6, 5, 7, 7,
+ 7, 7, 7, 7, 8, 8, 8, 9, 8, 8, 9, 9, 10, 11, 13, 12
+};
+
+// values in this table range from -1..34; adjust retrieved value by -1
+static const uint16 vlc_tab_fft_tone_offset_3_huffcodes[35] = {
+ 0x33ea, 0x0005, 0x0000, 0x000c, 0x0000, 0x0006, 0x0003, 0x0008,
+ 0x0002, 0x0001, 0x0004, 0x0007, 0x001a, 0x000f, 0x001c, 0x002c,
+ 0x000a, 0x001d, 0x002d, 0x002a, 0x000d, 0x004c, 0x008c, 0x006a,
+ 0x00cd, 0x004d, 0x00ea, 0x020c, 0x030c, 0x010c, 0x01ea, 0x07ea,
+ 0x0bea, 0x03ea, 0x13ea
+};
+
+static const byte vlc_tab_fft_tone_offset_3_huffbits[35] = {
+ 14, 4, 0, 10, 4, 3, 3, 4, 4, 3, 4, 4, 5, 4, 5, 6,
+ 6, 5, 6, 7, 7, 7, 8, 8, 8, 8, 9, 10, 10, 10, 10, 11,
+ 12, 13, 14
+};
+
+// values in this table range from -1..37; adjust retrieved value by -1
+static const uint16 vlc_tab_fft_tone_offset_4_huffcodes[38] = {
+ 0x5282, 0x0016, 0x0000, 0x0136, 0x0004, 0x0000, 0x0007, 0x000a,
+ 0x000e, 0x0003, 0x0001, 0x000d, 0x0006, 0x0009, 0x0012, 0x0005,
+ 0x0025, 0x0022, 0x0015, 0x0002, 0x0076, 0x0035, 0x0042, 0x00c2,
+ 0x0182, 0x00b6, 0x0036, 0x03c2, 0x0482, 0x01c2, 0x0682, 0x0882,
+ 0x0a82, 0x0082, 0x0282, 0x1282, 0x3282, 0x2282
+};
+
+static const byte vlc_tab_fft_tone_offset_4_huffbits[38] = {
+ 15, 6, 0, 9, 3, 3, 3, 4, 4, 3, 4, 4, 5, 4, 5, 6,
+ 6, 6, 6, 8, 7, 6, 8, 9, 9, 8, 9, 10, 11, 10, 11, 12,
+ 12, 12, 14, 15, 14, 14
+};
+
+/// FFT TABLES
+
+// values in this table range from -1..27; adjust retrieved value by -1
+static const uint16 fft_level_exp_alt_huffcodes[28] = {
+ 0x1ec6, 0x0006, 0x00c2, 0x0142, 0x0242, 0x0246, 0x00c6, 0x0046,
+ 0x0042, 0x0146, 0x00a2, 0x0062, 0x0026, 0x0016, 0x000e, 0x0005,
+ 0x0004, 0x0003, 0x0000, 0x0001, 0x000a, 0x0012, 0x0002, 0x0022,
+ 0x01c6, 0x02c6, 0x06c6, 0x0ec6
+};
+
+static const byte fft_level_exp_alt_huffbits[28] = {
+ 13, 7, 8, 9, 10, 10, 10, 10, 10, 9, 8, 7, 6, 5, 4, 3,
+ 3, 2, 3, 3, 4, 5, 7, 8, 9, 11, 12, 13
+};
+
+// values in this table range from -1..19; adjust retrieved value by -1
+static const uint16 fft_level_exp_huffcodes[20] = {
+ 0x0f24, 0x0001, 0x0002, 0x0000, 0x0006, 0x0005, 0x0007, 0x000c,
+ 0x000b, 0x0014, 0x0013, 0x0004, 0x0003, 0x0023, 0x0064, 0x00a4,
+ 0x0024, 0x0124, 0x0324, 0x0724
+};
+
+static const byte fft_level_exp_huffbits[20] = {
+ 12, 3, 3, 3, 3, 3, 3, 4, 4, 5, 5, 6, 6, 6, 7, 8, 9, 10, 11, 12
+};
+
+// values in this table range from -1..6; adjust retrieved value by -1
+static const byte fft_stereo_exp_huffcodes[7] = {
+ 0x3e, 0x01, 0x00, 0x02, 0x06, 0x0e, 0x1e
+};
+
+static const byte fft_stereo_exp_huffbits[7] = {
+ 6, 1, 2, 3, 4, 5, 6
+};
+
+// values in this table range from -1..8; adjust retrieved value by -1
+static const byte fft_stereo_phase_huffcodes[9] = {
+ 0x35, 0x02, 0x00, 0x01, 0x0d, 0x15, 0x05, 0x09, 0x03
+};
+
+static const byte fft_stereo_phase_huffbits[9] = {
+ 6, 2, 2, 4, 4, 6, 5, 4, 2
+};
+
+static const int fft_cutoff_index_table[4][2] = {
+ { 1, 2 }, {-1, 0 }, {-1,-2 }, { 0, 0 }
+};
+
+static const int16 fft_level_index_table[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+};
+
+static const byte last_coeff[3] = {
+ 4, 7, 10
+};
+
+static const byte coeff_per_sb_for_avg[3][30] = {
+ { 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+ { 0, 1, 2, 2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 },
+ { 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9 }
+};
+
+static const uint32 dequant_table[3][10][30] = {
+ { { 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 256, 256, 205, 154, 102, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 51, 102, 154, 205, 256, 238, 219, 201, 183, 165, 146, 128, 110, 91, 73, 55, 37, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 18, 37, 55, 73, 91, 110, 128, 146, 165, 183, 201, 219, 238, 256, 228, 199, 171, 142, 114, 85, 57, 28 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+ { { 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 256, 171, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 85, 171, 256, 171, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 85, 171, 256, 219, 183, 146, 110, 73, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 73, 110, 146, 183, 219, 256, 228, 199, 171, 142, 114, 85, 57, 28, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 57, 85, 114, 142, 171, 199, 228, 256, 213, 171, 128, 85, 43 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+ { { 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 256, 256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 256, 171, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 85, 171, 256, 192, 128, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 128, 192, 256, 205, 154, 102, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 102, 154, 205, 256, 213, 171, 128, 85, 43, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 85, 128, 171, 213, 256, 213, 171, 128, 85, 43 } }
+};
+
+static const byte coeff_per_sb_for_dequant[3][30] = {
+ { 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+ { 0, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6 },
+ { 0, 1, 2, 3, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9 }
+};
+
+// first index is subband, 2nd index is 0, 1 or 3 (2 is unused)
+static const int8 tone_level_idx_offset_table[30][4] = {
+ { -50, -50, 0, -50 },
+ { -50, -50, 0, -50 },
+ { -50, -9, 0, -19 },
+ { -16, -6, 0, -12 },
+ { -11, -4, 0, -8 },
+ { -8, -3, 0, -6 },
+ { -7, -3, 0, -5 },
+ { -6, -2, 0, -4 },
+ { -5, -2, 0, -3 },
+ { -4, -1, 0, -3 },
+ { -4, -1, 0, -2 },
+ { -3, -1, 0, -2 },
+ { -3, -1, 0, -2 },
+ { -3, -1, 0, -2 },
+ { -2, -1, 0, -1 },
+ { -2, -1, 0, -1 },
+ { -2, -1, 0, -1 },
+ { -2, 0, 0, -1 },
+ { -2, 0, 0, -1 },
+ { -1, 0, 0, -1 },
+ { -1, 0, 0, -1 },
+ { -1, 0, 0, -1 },
+ { -1, 0, 0, -1 },
+ { -1, 0, 0, -1 },
+ { -1, 0, 0, -1 },
+ { -1, 0, 0, -1 },
+ { -1, 0, 0, 0 },
+ { -1, 0, 0, 0 },
+ { -1, 0, 0, 0 },
+ { -1, 0, 0, 0 }
+};
+
+/* all my samples have 1st index 0 or 1 */
+/* second index is subband, only indexes 0-29 seem to be used */
+static const int8 coding_method_table[5][30] = {
+ { 34, 30, 24, 24, 16, 16, 16, 16, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10
+ },
+ { 34, 30, 24, 24, 16, 16, 16, 16, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10
+ },
+ { 34, 30, 30, 30, 24, 24, 16, 16, 16, 16, 16, 16, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10
+ },
+ { 34, 34, 30, 30, 24, 24, 24, 24, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 10, 10, 10, 10, 10, 10, 10, 10
+ },
+ { 34, 34, 30, 30, 30, 30, 30, 30, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16
+ },
+};
+
+static const int vlc_stage3_values[60] = {
+ 0, 1, 2, 3, 4, 6, 8, 10, 12, 16, 20, 24,
+ 28, 36, 44, 52, 60, 76, 92, 108, 124, 156, 188, 220,
+ 252, 316, 380, 444, 508, 636, 764, 892, 1020, 1276, 1532, 1788,
+ 2044, 2556, 3068, 3580, 4092, 5116, 6140, 7164, 8188, 10236, 12284, 14332,
+ 16380, 20476, 24572, 28668, 32764, 40956, 49148, 57340, 65532, 81916, 98300,114684
+};
+
+static const float fft_tone_sample_table[4][16][5] = {
+ { { .0100000000f,-.0037037037f,-.0020000000f,-.0069444444f,-.0018416207f },
+ { .0416666667f, .0000000000f, .0000000000f,-.0208333333f,-.0123456791f },
+ { .1250000000f, .0558035709f, .0330687836f,-.0164473690f,-.0097465888f },
+ { .1562500000f, .0625000000f, .0370370370f,-.0062500000f,-.0037037037f },
+ { .1996007860f, .0781250000f, .0462962948f, .0022727272f, .0013468013f },
+ { .2000000000f, .0625000000f, .0370370373f, .0208333333f, .0074074073f },
+ { .2127659619f, .0555555556f, .0329218097f, .0208333333f, .0123456791f },
+ { .2173913121f, .0473484844f, .0280583613f, .0347222239f, .0205761325f },
+ { .2173913121f, .0347222239f, .0205761325f, .0473484844f, .0280583613f },
+ { .2127659619f, .0208333333f, .0123456791f, .0555555556f, .0329218097f },
+ { .2000000000f, .0208333333f, .0074074073f, .0625000000f, .0370370370f },
+ { .1996007860f, .0022727272f, .0013468013f, .0781250000f, .0462962948f },
+ { .1562500000f,-.0062500000f,-.0037037037f, .0625000000f, .0370370370f },
+ { .1250000000f,-.0164473690f,-.0097465888f, .0558035709f, .0330687836f },
+ { .0416666667f,-.0208333333f,-.0123456791f, .0000000000f, .0000000000f },
+ { .0100000000f,-.0069444444f,-.0018416207f,-.0037037037f,-.0020000000f } },
+
+ { { .0050000000f,-.0200000000f, .0125000000f,-.3030303030f, .0020000000f },
+ { .1041666642f, .0400000000f,-.0250000000f, .0333333333f,-.0200000000f },
+ { .1250000000f, .0100000000f, .0142857144f,-.0500000007f,-.0200000000f },
+ { .1562500000f,-.0006250000f,-.00049382716f,-.000625000f,-.00049382716f },
+ { .1562500000f,-.0006250000f,-.00049382716f,-.000625000f,-.00049382716f },
+ { .1250000000f,-.0500000000f,-.0200000000f, .0100000000f, .0142857144f },
+ { .1041666667f, .0333333333f,-.0200000000f, .0400000000f,-.0250000000f },
+ { .0050000000f,-.3030303030f, .0020000001f,-.0200000000f, .0125000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f } },
+
+ { { .1428571492f, .1250000000f,-.0285714287f,-.0357142873f, .0208333333f },
+ { .1818181818f, .0588235296f, .0333333333f, .0212765951f, .0100000000f },
+ { .1818181818f, .0212765951f, .0100000000f, .0588235296f, .0333333333f },
+ { .1428571492f,-.0357142873f, .0208333333f, .1250000000f,-.0285714287f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f } },
+
+ { { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f },
+ { .0000000000f, .0000000000f, .0000000000f, .0000000000f, .0000000000f } }
+};
+
+static const float fft_tone_level_table[2][64] = { {
+// pow ~ (i > 46) ? 0 : (((((i & 1) ? 431 : 304) << (i >> 1))) / 1024.0);
+ 0.17677669f, 0.42677650f, 0.60355347f, 0.85355347f,
+ 1.20710683f, 1.68359375f, 2.37500000f, 3.36718750f,
+ 4.75000000f, 6.73437500f, 9.50000000f, 13.4687500f,
+ 19.0000000f, 26.9375000f, 38.0000000f, 53.8750000f,
+ 76.0000000f, 107.750000f, 152.000000f, 215.500000f,
+ 304.000000f, 431.000000f, 608.000000f, 862.000000f,
+ 1216.00000f, 1724.00000f, 2432.00000f, 3448.00000f,
+ 4864.00000f, 6896.00000f, 9728.00000f, 13792.0000f,
+ 19456.0000f, 27584.0000f, 38912.0000f, 55168.0000f,
+ 77824.0000f, 110336.000f, 155648.000f, 220672.000f,
+ 311296.000f, 441344.000f, 622592.000f, 882688.000f,
+ 1245184.00f, 1765376.00f, 2490368.00f, 0.00000000f,
+ 0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f,
+ 0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f,
+ 0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f,
+ 0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f,
+ }, {
+// pow = (i > 45) ? 0 : ((((i & 1) ? 431 : 304) << (i >> 1)) / 512.0);
+ 0.59375000f, 0.84179688f, 1.18750000f, 1.68359375f,
+ 2.37500000f, 3.36718750f, 4.75000000f, 6.73437500f,
+ 9.50000000f, 13.4687500f, 19.0000000f, 26.9375000f,
+ 38.0000000f, 53.8750000f, 76.0000000f, 107.750000f,
+ 152.000000f, 215.500000f, 304.000000f, 431.000000f,
+ 608.000000f, 862.000000f, 1216.00000f, 1724.00000f,
+ 2432.00000f, 3448.00000f, 4864.00000f, 6896.00000f,
+ 9728.00000f, 13792.0000f, 19456.0000f, 27584.0000f,
+ 38912.0000f, 55168.0000f, 77824.0000f, 110336.000f,
+ 155648.000f, 220672.000f, 311296.000f, 441344.000f,
+ 622592.000f, 882688.000f, 1245184.00f, 1765376.00f,
+ 2490368.00f, 3530752.00f, 0.00000000f, 0.00000000f,
+ 0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f,
+ 0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f,
+ 0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f,
+ 0.00000000f, 0.00000000f, 0.00000000f, 0.00000000f
+} };
+
+static const float fft_tone_envelope_table[4][31] = {
+ { .009607375f, .038060248f, .084265202f, .146446645f, .222214907f, .308658302f,
+ .402454883f, .500000060f, .597545207f, .691341758f, .777785182f, .853553414f,
+ .915734828f, .961939812f, .990392685f, 1.00000000f, .990392625f, .961939752f,
+ .915734768f, .853553295f, .777785063f, .691341639f, .597545087f, .500000000f,
+ .402454853f, .308658272f, .222214878f, .146446615f, .084265172f, .038060218f,
+ .009607345f },
+ { .038060248f, .146446645f, .308658302f, .500000060f, .691341758f, .853553414f,
+ .961939812f, 1.00000000f, .961939752f, .853553295f, .691341639f, .500000000f,
+ .308658272f, .146446615f, .038060218f, .000000000f, .000000000f, .000000000f,
+ .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
+ .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
+ .000000000f },
+ { .146446645f, .500000060f, .853553414f, 1.00000000f, .853553295f, .500000000f,
+ .146446615f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
+ .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
+ .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
+ .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
+ .000000000f },
+ { .500000060f, 1.00000000f, .500000000f, .000000000f, .000000000f, .000000000f,
+ .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
+ .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
+ .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
+ .000000000f, .000000000f, .000000000f, .000000000f, .000000000f, .000000000f,
+ .000000000f }
+};
+
+static const float sb_noise_attenuation[32] = {
+ 0.0f, 0.0f, 0.3f, 0.4f, 0.5f, 0.7f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
+};
+
+static const byte fft_subpackets[32] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0
+};
+
+// first index is joined_stereo, second index is 0 or 2 (1 is unused)
+static const float dequant_1bit[2][3] = {
+ {-0.920000f, 0.000000f, 0.920000f },
+ {-0.890000f, 0.000000f, 0.890000f }
+};
+
+static const float type30_dequant[8] = {
+ -1.0f,-0.625f,-0.291666656732559f,0.0f,
+ 0.25f,0.5f,0.75f,1.0f,
+};
+
+static const float type34_delta[10] = { // FIXME: covers 8 entries..
+ -1.0f,-0.60947573184967f,-0.333333343267441f,-0.138071194291115f,0.0f,
+ 0.138071194291115f,0.333333343267441f,0.60947573184967f,1.0f,0.0f,
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/video/qt_player.cpp b/engines/mohawk/video/qt_player.cpp
new file mode 100644
index 0000000000..9df5a3c930
--- /dev/null
+++ b/engines/mohawk/video/qt_player.cpp
@@ -0,0 +1,1097 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+//
+// Heavily based on ffmpeg code.
+//
+// Copyright (c) 2001 Fabrice Bellard.
+// First version by Francois Revol revol@free.fr
+// Seek function by Gael Chardon gael.dev@4now.net
+//
+
+#include "mohawk/video/qt_player.h"
+
+#include "common/endian.h"
+#include "common/util.h"
+#include "common/zlib.h"
+
+// Audio codecs
+#include "sound/adpcm.h"
+#include "mohawk/video/qdm2.h"
+
+namespace Mohawk {
+
+QTPlayer::QTPlayer() : Video() {
+ _audStream = NULL;
+}
+
+QTPlayer::~QTPlayer() {
+ closeFile();
+}
+
+uint16 QTPlayer::getWidth() {
+ if (_videoStreamIndex < 0)
+ return 0;
+
+ return _streams[_videoStreamIndex]->width;
+}
+
+uint16 QTPlayer::getHeight() {
+ if (_videoStreamIndex < 0)
+ return 0;
+
+ return _streams[_videoStreamIndex]->height;
+}
+
+uint32 QTPlayer::getFrameCount() {
+ if (_videoStreamIndex < 0)
+ return 0;
+
+ return _streams[_videoStreamIndex]->nb_frames;
+}
+
+byte QTPlayer::getBitsPerPixel() {
+ if (_videoStreamIndex < 0)
+ return 0;
+
+ return _streams[_videoStreamIndex]->bits_per_sample & 0x1F;
+}
+
+uint32 QTPlayer::getCodecTag() {
+ if (_videoStreamIndex < 0)
+ return 0;
+
+ return _streams[_videoStreamIndex]->codec_tag;
+}
+
+ScaleMode QTPlayer::getScaleMode() {
+ if (_videoStreamIndex < 0)
+ return kScaleNormal;
+
+ return (ScaleMode)(_scaleMode * _streams[_videoStreamIndex]->scaleMode);
+}
+
+uint32 QTPlayer::getFrameDuration(uint32 frame) {
+ if (_videoStreamIndex < 0)
+ return 0;
+
+ uint32 curFrameIndex = 0;
+ for (int32 i = 0; i < _streams[_videoStreamIndex]->stts_count; i++) {
+ curFrameIndex += _streams[_videoStreamIndex]->stts_data[i].count;
+ if (frame < curFrameIndex) {
+ // Ok, now we have what duration this frame has. Now, we have to convert the duration to 1/100 ms.
+ return _streams[_videoStreamIndex]->stts_data[i].duration * 1000 * 100 / _streams[_videoStreamIndex]->time_scale;
+ }
+ }
+
+ // This should never occur
+ error ("Cannot find duration for frame %d", frame);
+ return 0;
+}
+
+bool QTPlayer::loadFile(Common::SeekableReadStream *stream) {
+ _fd = stream;
+ _foundMOOV = _foundMDAT = false;
+ _numStreams = 0;
+ _partial = 0;
+ _videoStreamIndex = _audioStreamIndex = -1;
+
+ initParseTable();
+
+ MOVatom atom = { 0, 0, 0xffffffff };
+
+ if (readDefault(atom) < 0 || (!_foundMOOV && !_foundMDAT))
+ return false;
+
+ debug(0, "on_parse_exit_offset=%d", _fd->pos());
+
+ // some cleanup : make sure we are on the mdat atom
+ if((uint32)_fd->pos() != _mdatOffset)
+ _fd->seek(_mdatOffset, SEEK_SET);
+
+ _next_chunk_offset = _mdatOffset; // initialise reading
+
+ for (uint32 i = 0; i < _numStreams;) {
+ if (_streams[i]->codec_type == CODEC_TYPE_MOV_OTHER) {// not audio, not video, delete
+ delete _streams[i];
+ for (uint32 j = i + 1; j < _numStreams; j++)
+ _streams[j - 1] = _streams[j];
+ _numStreams--;
+ } else
+ i++;
+ }
+
+ for (uint32 i = 0; i < _numStreams; i++) {
+ MOVStreamContext *sc = _streams[i];
+
+ if(!sc->time_rate)
+ sc->time_rate = 1;
+
+ if(!sc->time_scale)
+ sc->time_scale = _timeScale;
+
+ //av_set_pts_info(s->streams[i], 64, sc->time_rate, sc->time_scale);
+
+ sc->duration /= sc->time_rate;
+
+ sc->ffindex = i;
+ sc->is_ff_stream = 1;
+
+ if (sc->codec_type == CODEC_TYPE_VIDEO && _videoStreamIndex < 0)
+ _videoStreamIndex = i;
+ else if (sc->codec_type == CODEC_TYPE_AUDIO && _audioStreamIndex < 0)
+ _audioStreamIndex = i;
+ }
+
+ if (_audioStreamIndex >= 0 && checkAudioCodecSupport(_streams[_audioStreamIndex]->codec_tag)) {
+ _audStream = new QueuedAudioStream(_streams[_audioStreamIndex]->sample_rate, _streams[_audioStreamIndex]->channels);
+ _curAudioChunk = 0;
+
+ // Make sure the bits per sample transfers to the sample size
+ if (_streams[_audioStreamIndex]->codec_tag == MKID_BE('raw ') || _streams[_audioStreamIndex]->codec_tag == MKID_BE('twos'))
+ _streams[_audioStreamIndex]->sample_size = (_streams[_audioStreamIndex]->bits_per_sample / 8) * _streams[_audioStreamIndex]->channels;
+ }
+
+ return true;
+}
+
+void QTPlayer::initParseTable() {
+ static const ParseTable p[] = {
+ { MKID_BE('dinf'), &QTPlayer::readDefault },
+ { MKID_BE('dref'), &QTPlayer::readLeaf },
+ { MKID_BE('edts'), &QTPlayer::readDefault },
+ { MKID_BE('elst'), &QTPlayer::readELST },
+ { MKID_BE('hdlr'), &QTPlayer::readHDLR },
+ { MKID_BE('mdat'), &QTPlayer::readMDAT },
+ { MKID_BE('mdhd'), &QTPlayer::readMDHD },
+ { MKID_BE('mdia'), &QTPlayer::readDefault },
+ { MKID_BE('minf'), &QTPlayer::readDefault },
+ { MKID_BE('moov'), &QTPlayer::readMOOV },
+ { MKID_BE('mvhd'), &QTPlayer::readMVHD },
+ { MKID_BE('smhd'), &QTPlayer::readLeaf },
+ { MKID_BE('stbl'), &QTPlayer::readDefault },
+ { MKID_BE('stco'), &QTPlayer::readSTCO },
+ { MKID_BE('stsc'), &QTPlayer::readSTSC },
+ { MKID_BE('stsd'), &QTPlayer::readSTSD },
+ { MKID_BE('stss'), &QTPlayer::readSTSS },
+ { MKID_BE('stsz'), &QTPlayer::readSTSZ },
+ { MKID_BE('stts'), &QTPlayer::readSTTS },
+ { MKID_BE('tkhd'), &QTPlayer::readTKHD },
+ { MKID_BE('trak'), &QTPlayer::readTRAK },
+ { MKID_BE('udta'), &QTPlayer::readLeaf },
+ { MKID_BE('vmhd'), &QTPlayer::readLeaf },
+ { MKID_BE('cmov'), &QTPlayer::readCMOV },
+ { MKID_BE('wave'), &QTPlayer::readWAVE },
+ { 0, 0 }
+ };
+
+ _parseTable = p;
+}
+
+int QTPlayer::readDefault(MOVatom atom) {
+ uint32 total_size = 0;
+ MOVatom a;
+ int err = 0;
+
+ a.offset = atom.offset;
+
+ while(((total_size + 8) < atom.size) && !_fd->eos() && !err) {
+ a.size = atom.size;
+ a.type = 0;
+
+ if (atom.size >= 8) {
+ a.size = _fd->readUint32BE();
+ a.type = _fd->readUint32BE();
+ }
+
+ total_size += 8;
+ a.offset += 8;
+ debug(4, "type: %08x %.4s sz: %x %x %x", a.type, tag2str(a.type), a.size, atom.size, total_size);
+
+ if (a.size == 1) { // 64 bit extended size
+ warning("64 bit extended size is not supported in QuickTime");
+ return -1;
+ }
+
+ if (a.size == 0) {
+ a.size = atom.size - total_size;
+ if (a.size <= 8)
+ break;
+ }
+
+ uint32 i = 0;
+
+ for (; _parseTable[i].type != 0 && _parseTable[i].type != a.type; i++)
+ // empty;
+
+ if (a.size < 8)
+ break;
+
+ a.size -= 8;
+
+ if (_parseTable[i].type == 0) { // skip leaf atoms data
+ debug(0, ">>> Skipped [%s]", tag2str(a.type));
+
+ _fd->seek(a.size, SEEK_CUR);
+ } else {
+ uint32 start_pos = _fd->pos();
+ err = (this->*_parseTable[i].func)(a);
+
+ uint32 left = a.size - _fd->pos() + start_pos;
+
+ if (left > 0) // skip garbage at atom end
+ _fd->seek(left, SEEK_CUR);
+ }
+
+ a.offset += a.size;
+ total_size += a.size;
+ }
+
+ if (!err && total_size < atom.size)
+ _fd->seek(atom.size - total_size, SEEK_SET);
+
+ return err;
+}
+
+int QTPlayer::readLeaf(MOVatom atom) {
+ if (atom.size > 1)
+ _fd->seek(atom.size, SEEK_SET);
+
+ return 0;
+}
+
+int QTPlayer::readMOOV(MOVatom atom) {
+ if (readDefault(atom) < 0)
+ return -1;
+
+ // we parsed the 'moov' atom, we can terminate the parsing as soon as we find the 'mdat'
+ // so we don't parse the whole file if over a network
+ _foundMOOV = true;
+
+ if(_foundMDAT)
+ return 1; // found both, just go
+
+ return 0; // now go for mdat
+}
+
+int QTPlayer::readCMOV(MOVatom atom) {
+#ifdef USE_ZLIB
+ // Read in the dcom atom
+ _fd->readUint32BE();
+ if (_fd->readUint32BE() != MKID_BE('dcom'))
+ return -1;
+ if (_fd->readUint32BE() != MKID_BE('zlib')) {
+ warning("Unknown cmov compression type");
+ return -1;
+ }
+
+ // Read in the cmvd atom
+ uint32 compressedSize = _fd->readUint32BE() - 12;
+ if (_fd->readUint32BE() != MKID_BE('cmvd'))
+ return -1;
+ uint32 uncompressedSize = _fd->readUint32BE();
+
+ // Read in data
+ byte *compressedData = (byte *)malloc(compressedSize);
+ _fd->read(compressedData, compressedSize);
+
+ // Create uncompressed stream
+ byte *uncompressedData = (byte *)malloc(uncompressedSize);
+
+ // Uncompress the data
+ unsigned long dstLen = uncompressedSize;
+ if (!Common::uncompress(uncompressedData, &dstLen, compressedData, compressedSize)) {
+ warning ("Could not uncompress cmov chunk");
+ return -1;
+ }
+
+ // Load data into a new MemoryReadStream and assign _fd to be that
+ Common::SeekableReadStream *oldStream = _fd;
+ _fd = new Common::MemoryReadStream(uncompressedData, uncompressedSize, Common::DisposeAfterUse::YES);
+
+ // Read the contents of the uncompressed data
+ MOVatom a = { MKID_BE('moov'), 0, uncompressedSize };
+ int err = readDefault(a);
+
+ // Assign the file handle back to the original handle
+ free(compressedData);
+ delete _fd;
+ _fd = oldStream;
+
+ return err;
+#else
+ warning ("zlib not found, cannot read QuickTime cmov atom");
+ return -1;
+#endif
+}
+
+int QTPlayer::readMVHD(MOVatom atom) {
+ byte version = _fd->readByte(); // version
+ _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags
+
+ if (version == 1) {
+ warning("QuickTime version 1");
+ _fd->readUint32BE(); _fd->readUint32BE();
+ _fd->readUint32BE(); _fd->readUint32BE();
+ } else {
+ _fd->readUint32BE(); // creation time
+ _fd->readUint32BE(); // modification time
+ }
+
+ _timeScale = _fd->readUint32BE(); // time scale
+ debug(0, "time scale = %i\n", _timeScale);
+
+ // duration
+ _duration = (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE();
+ _fd->readUint32BE(); // preferred scale
+
+ _fd->readUint16BE(); // preferred volume
+
+ _fd->seek(10, SEEK_CUR); // reserved
+
+ // We only need two values from the movie display matrix. Most of the values are just
+ // skipped. xMod and yMod are 16:16 fixed point numbers, the last part of the 3x3 matrix
+ // is 2:30.
+ uint32 xMod = _fd->readUint32BE();
+ _fd->skip(12);
+ uint32 yMod = _fd->readUint32BE();
+ _fd->skip(16);
+
+ if (xMod != yMod)
+ error("X and Y resolution modifiers differ");
+
+ if (xMod == 0x8000)
+ _scaleMode = kScaleHalf;
+ else if (xMod == 0x4000)
+ _scaleMode = kScaleQuarter;
+ else
+ _scaleMode = kScaleNormal;
+
+ debug(1, "readMVHD(): scaleMode = %d", (int)_scaleMode);
+
+ _fd->readUint32BE(); // preview time
+ _fd->readUint32BE(); // preview duration
+ _fd->readUint32BE(); // poster time
+ _fd->readUint32BE(); // selection time
+ _fd->readUint32BE(); // selection duration
+ _fd->readUint32BE(); // current time
+ _fd->readUint32BE(); // next track ID
+
+ return 0;
+}
+
+int QTPlayer::readTRAK(MOVatom atom) {
+ MOVStreamContext *sc = new MOVStreamContext();
+
+ if (!sc)
+ return -1;
+
+ sc->sample_to_chunk_index = -1;
+ sc->codec_type = CODEC_TYPE_MOV_OTHER;
+ sc->start_time = 0; // XXX: check
+ _streams[_numStreams++] = sc;
+
+ return readDefault(atom);
+}
+
+// this atom contains actual media data
+int QTPlayer::readMDAT(MOVatom atom) {
+ if (atom.size == 0) // wrong one (MP4)
+ return 0;
+
+ _foundMDAT = true;
+
+ _mdatOffset = atom.offset;
+ _mdatSize = atom.size;
+
+ if (_foundMOOV)
+ return 1; // found both, just go
+
+ _fd->seek(atom.size, SEEK_CUR);
+
+ return 0; // now go for moov
+}
+
+int QTPlayer::readTKHD(MOVatom atom) {
+ MOVStreamContext *st = _streams[_numStreams - 1];
+ byte version = _fd->readByte();
+
+ _fd->readByte(); _fd->readByte();
+ _fd->readByte(); // flags
+ //
+ //MOV_TRACK_ENABLED 0x0001
+ //MOV_TRACK_IN_MOVIE 0x0002
+ //MOV_TRACK_IN_PREVIEW 0x0004
+ //MOV_TRACK_IN_POSTER 0x0008
+ //
+
+ if (version == 1) {
+ _fd->readUint32BE(); _fd->readUint32BE();
+ _fd->readUint32BE(); _fd->readUint32BE();
+ } else {
+ _fd->readUint32BE(); // creation time
+ _fd->readUint32BE(); // modification time
+ }
+
+ /* st->id = */_fd->readUint32BE(); // track id (NOT 0 !)
+ _fd->readUint32BE(); // reserved
+ //st->start_time = 0; // check
+ (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); // highlevel (considering edits) duration in movie timebase
+ _fd->readUint32BE(); // reserved
+ _fd->readUint32BE(); // reserved
+
+ _fd->readUint16BE(); // layer
+ _fd->readUint16BE(); // alternate group
+ _fd->readUint16BE(); // volume
+ _fd->readUint16BE(); // reserved
+
+ // We only need the two values from the displacement matrix for a track.
+ // See readMVHD() for more information.
+ uint32 xMod = _fd->readUint32BE();
+ _fd->skip(12);
+ uint32 yMod = _fd->readUint32BE();
+ _fd->skip(16);
+
+ if (xMod != yMod)
+ error("X and Y resolution modifiers differ");
+
+ if (xMod == 0x8000)
+ st->scaleMode = kScaleHalf;
+ else if (xMod == 0x4000)
+ st->scaleMode = kScaleQuarter;
+ else
+ st->scaleMode = kScaleNormal;
+
+ debug(1, "readTKHD(): scaleMode = %d", (int)_scaleMode);
+
+ // these are fixed-point, 16:16
+ // uint32 tkWidth = _fd->readUint32BE() >> 16; // track width
+ // uint32 tkHeight = _fd->readUint32BE() >> 16; // track height
+
+ return 0;
+}
+
+// edit list atom
+int QTPlayer::readELST(MOVatom atom) {
+ _fd->readByte(); // version
+ _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags
+ uint32 editCount = _streams[_numStreams - 1]->edit_count = _fd->readUint32BE(); // entries
+
+ for (uint32 i = 0; i < editCount; i++){
+ _fd->readUint32BE(); // Track duration
+ _fd->readUint32BE(); // Media time
+ _fd->readUint32BE(); // Media rate
+ }
+
+ debug(0, "track[%i].edit_count = %i", _numStreams - 1, _streams[_numStreams - 1]->edit_count);
+
+ if (editCount != 1)
+ warning("Multiple edit list entries. Things may go awry");
+
+ return 0;
+}
+
+int QTPlayer::readHDLR(MOVatom atom) {
+ MOVStreamContext *st = _streams[_numStreams - 1];
+
+ _fd->readByte(); // version
+ _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags
+
+ // component type
+ uint32 ctype = _fd->readUint32LE();
+ uint32 type = _fd->readUint32BE(); // component subtype
+
+ debug(0, "ctype= %s (0x%08lx)", tag2str(ctype), (long)ctype);
+ debug(0, "stype= %s", tag2str(type));
+
+ if(ctype == MKID_BE('mhlr')) // MOV
+ debug(0, "MOV detected");
+ else if(ctype == 0) {
+ warning("MP4 streams are not supported");
+ return -1;
+ }
+
+ if (type == MKID_BE('vide'))
+ st->codec_type = CODEC_TYPE_VIDEO;
+ else if (type == MKID_BE('soun'))
+ st->codec_type = CODEC_TYPE_AUDIO;
+
+ _fd->readUint32BE(); // component manufacture
+ _fd->readUint32BE(); // component flags
+ _fd->readUint32BE(); // component flags mask
+
+ if (atom.size <= 24)
+ return 0; // nothing left to read
+
+ // .mov: PASCAL string
+ byte len = _fd->readByte();
+ _fd->seek(len, SEEK_CUR);
+
+ _fd->seek(atom.size - (_fd->pos() - atom.offset), SEEK_CUR);
+
+ return 0;
+}
+
+int QTPlayer::readMDHD(MOVatom atom) {
+ MOVStreamContext *st = _streams[_numStreams - 1];
+ byte version = _fd->readByte();
+
+ if (version > 1)
+ return 1; // unsupported
+
+ _fd->readByte(); _fd->readByte();
+ _fd->readByte(); // flags
+
+ if (version == 1) {
+ _fd->readUint32BE(); _fd->readUint32BE();
+ _fd->readUint32BE(); _fd->readUint32BE();
+ } else {
+ _fd->readUint32BE(); // creation time
+ _fd->readUint32BE(); // modification time
+ }
+
+ st->time_scale = _fd->readUint32BE();
+ st->duration = (version == 1) ? (_fd->readUint32BE(), _fd->readUint32BE()) : _fd->readUint32BE(); // duration
+
+ _fd->readUint16BE(); // language
+ _fd->readUint16BE(); // quality
+
+ return 0;
+}
+
+int QTPlayer::readSTSD(MOVatom atom) {
+ MOVStreamContext *st = _streams[_numStreams - 1];
+
+ _fd->readByte(); // version
+ _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags
+
+ uint32 entries = _fd->readUint32BE();
+
+ while (entries--) { //Parsing Sample description table
+ MOVatom a = { 0, 0, 0 };
+ uint32 start_pos = _fd->pos();
+ int size = _fd->readUint32BE(); // size
+ uint32 format = _fd->readUint32BE(); // data format
+
+ _fd->readUint32BE(); // reserved
+ _fd->readUint16BE(); // reserved
+ _fd->readUint16BE(); // index
+
+ debug(0, "size=%d 4CC= %s codec_type=%d", size, tag2str(format), st->codec_type);
+ st->codec_tag = format;
+
+ if (st->codec_type == CODEC_TYPE_VIDEO) {
+ debug(0, "Video Codec FourCC: \'%s\'", tag2str(format));
+
+ _fd->readUint16BE(); // version
+ _fd->readUint16BE(); // revision level
+ _fd->readUint32BE(); // vendor
+ _fd->readUint32BE(); // temporal quality
+ _fd->readUint32BE(); // spacial quality
+
+ st->width = _fd->readUint16BE(); // width
+ st->height = _fd->readUint16BE(); // height
+
+ _fd->readUint32BE(); // horiz resolution
+ _fd->readUint32BE(); // vert resolution
+ _fd->readUint32BE(); // data size, always 0
+ uint16 frames_per_sample = _fd->readUint16BE(); // frames per samples
+
+ debug(0, "frames/samples = %d", frames_per_sample);
+
+ byte codec_name[32];
+ _fd->read(codec_name, 32); // codec name, pascal string (FIXME: true for mp4?)
+ if (codec_name[0] <= 31) {
+ memcpy(st->codec_name, &codec_name[1], codec_name[0]);
+ st->codec_name[codec_name[0]] = 0;
+ }
+
+ st->bits_per_sample = _fd->readUint16BE(); // depth
+ st->color_table_id = _fd->readUint16BE(); // colortable id
+
+// These are set in mov_read_stts and might already be set!
+// st->codec->time_base.den = 25;
+// st->codec->time_base.num = 1;
+
+
+ // figure out the palette situation
+ byte colorDepth = st->bits_per_sample & 0x1F;
+ bool colorGreyscale = (st->bits_per_sample & 0x20) != 0;
+
+ debug(0, "color depth: %d", colorDepth);
+
+ // if the depth is 2, 4, or 8 bpp, file is palettized
+ if (colorDepth == 2 || colorDepth == 4 || colorDepth == 8) {
+ if (colorGreyscale) {
+ debug(0, "Greyscale palette");
+
+ // compute the greyscale palette
+ uint16 colorCount = 1 << colorDepth;
+ int16 colorIndex = 255;
+ byte colorDec = 256 / (colorCount - 1);
+ for (byte j = 0; j < colorCount; j++) {
+ _palette[j * 4] = _palette[j * 4 + 1] = _palette[j * 4 + 2] = colorIndex;
+ colorIndex -= colorDec;
+ if (colorIndex < 0)
+ colorIndex = 0;
+ }
+ } else if (st->color_table_id & 0x08) {
+ // if flag bit 3 is set, use the default palette
+ //uint16 colorCount = 1 << colorDepth;
+
+ warning("Predefined palette! %dbpp", colorDepth);
+#if 0
+ byte *color_table;
+ byte r, g, b;
+
+ if (colorDepth == 2)
+ color_table = ff_qt_default_palette_4;
+ else if (colorDepth == 4)
+ color_table = ff_qt_default_palette_16;
+ else
+ color_table = ff_qt_default_palette_256;
+
+ for (byte j = 0; j < color_count; j++) {
+ r = color_table[j * 4 + 0];
+ g = color_table[j * 4 + 1];
+ b = color_table[j * 4 + 2];
+ _palette_control.palette[j] = (r << 16) | (g << 8) | (b);
+ }
+#endif
+
+ } else {
+ debug(0, "Palette from file");
+
+ // load the palette from the file
+ uint32 colorStart = _fd->readUint32BE();
+ /* uint16 colorCount = */ _fd->readUint16BE();
+ uint16 colorEnd = _fd->readUint16BE();
+ for (uint32 j = colorStart; j <= colorEnd; j++) {
+ // each R, G, or B component is 16 bits;
+ // only use the top 8 bits; skip alpha bytes
+ // up front
+ _fd->readByte();
+ _fd->readByte();
+ _palette[j * 4] = _fd->readByte();
+ _fd->readByte();
+ _palette[j * 4 + 1] = _fd->readByte();
+ _fd->readByte();
+ _palette[j * 4 + 2] = _fd->readByte();
+ _fd->readByte();
+ }
+ }
+ st->palettized = true;
+ } else
+ st->palettized = false;
+ } else if (st->codec_type == CODEC_TYPE_AUDIO) {
+ debug(0, "Audio Codec FourCC: \'%s\'", tag2str(format));
+
+ st->stsd_version = _fd->readUint16BE();
+ _fd->readUint16BE(); // revision level
+ _fd->readUint32BE(); // vendor
+
+ st->channels = _fd->readUint16BE(); // channel count
+ st->bits_per_sample = _fd->readUint16BE(); // sample size
+ // do we need to force to 16 for AMR ?
+
+ // handle specific s8 codec
+ _fd->readUint16BE(); // compression id = 0
+ _fd->readUint16BE(); // packet size = 0
+
+ st->sample_rate = (_fd->readUint32BE() >> 16);
+
+ debug(0, "stsd version =%d", st->stsd_version);
+ if (st->stsd_version == 0) {
+ // Not used, except in special cases. See below.
+ st->samples_per_frame = st->bytes_per_frame = 0;
+ } else if (st->stsd_version == 1) {
+ // Read QT version 1 fields. In version 0 these dont exist.
+ st->samples_per_frame = _fd->readUint32BE();
+ debug(0, "stsd samples_per_frame =%d", st->samples_per_frame);
+ _fd->readUint32BE(); // bytes per packet
+ st->bytes_per_frame = _fd->readUint32BE();
+ debug(0, "stsd bytes_per_frame =%d", st->bytes_per_frame);
+ _fd->readUint32BE(); // bytes per sample
+ } else {
+ warning("Unsupported QuickTime STSD audio version %d", st->stsd_version);
+ return 1;
+ }
+
+ // Version 0 videos (such as the Riven ones) don't have this set,
+ // but we need it later on. Add it in here.
+ if (format == MKID_BE('ima4')) {
+ st->samples_per_frame = 64;
+ st->bytes_per_frame = 34 * st->channels;
+ }
+ } else {
+ // other codec type, just skip (rtp, mp4s, tmcd ...)
+ _fd->seek(size - (_fd->pos() - start_pos), SEEK_CUR);
+ }
+
+ // this will read extra atoms at the end (wave, alac, damr, avcC, SMI ...)
+ a.size = size - (_fd->pos() - start_pos);
+ if (a.size > 8)
+ readDefault(a);
+ else if (a.size > 0)
+ _fd->seek(a.size, SEEK_CUR);
+ }
+
+ if (st->codec_type == CODEC_TYPE_AUDIO && st->sample_rate == 0 && st->time_scale > 1)
+ st->sample_rate= st->time_scale;
+
+ return 0;
+}
+
+int QTPlayer::readSTSC(MOVatom atom) {
+ MOVStreamContext *st = _streams[_numStreams - 1];
+
+ _fd->readByte(); // version
+ _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags
+
+ st->sample_to_chunk_sz = _fd->readUint32BE();
+
+ debug(0, "track[%i].stsc.entries = %i", _numStreams - 1, st->sample_to_chunk_sz);
+
+ st->sample_to_chunk = new MOVstsc[st->sample_to_chunk_sz];
+
+ if (!st->sample_to_chunk)
+ return -1;
+
+ for (uint32 i = 0; i < st->sample_to_chunk_sz; i++) {
+ st->sample_to_chunk[i].first = _fd->readUint32BE();
+ st->sample_to_chunk[i].count = _fd->readUint32BE();
+ st->sample_to_chunk[i].id = _fd->readUint32BE();
+ //printf ("Sample to Chunk[%d]: First = %d, Count = %d\n", i, st->sample_to_chunk[i].first, st->sample_to_chunk[i].count);
+ }
+
+ return 0;
+}
+
+int QTPlayer::readSTSS(MOVatom atom) {
+ MOVStreamContext *st = _streams[_numStreams - 1];
+
+ _fd->readByte(); // version
+ _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags
+
+ st->keyframe_count = _fd->readUint32BE();
+
+ debug(0, "keyframe_count = %d", st->keyframe_count);
+
+ st->keyframes = new uint32[st->keyframe_count];
+
+ if (!st->keyframes)
+ return -1;
+
+ for (uint32 i = 0; i < st->keyframe_count; i++) {
+ st->keyframes[i] = _fd->readUint32BE();
+ debug(6, "keyframes[%d] = %d", i, st->keyframes[i]);
+
+ }
+ return 0;
+}
+
+int QTPlayer::readSTSZ(MOVatom atom) {
+ MOVStreamContext *st = _streams[_numStreams - 1];
+
+ _fd->readByte(); // version
+ _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags
+
+ st->sample_size = _fd->readUint32BE();
+ st->sample_count = _fd->readUint32BE();
+
+ debug(5, "sample_size = %d sample_count = %d", st->sample_size, st->sample_count);
+
+ if (st->sample_size)
+ return 0; // there isn't any table following
+
+ st->sample_sizes = new uint32[st->sample_count];
+
+ if (!st->sample_sizes)
+ return -1;
+
+ for(uint32 i = 0; i < st->sample_count; i++) {
+ st->sample_sizes[i] = _fd->readUint32BE();
+ debug(6, "sample_sizes[%d] = %d", i, st->sample_sizes[i]);
+ }
+
+ return 0;
+}
+
+static uint32 ff_gcd(uint32 a, uint32 b) {
+ if(b) return ff_gcd(b, a%b);
+ else return a;
+}
+
+int QTPlayer::readSTTS(MOVatom atom) {
+ MOVStreamContext *st = _streams[_numStreams - 1];
+ uint32 duration = 0;
+ uint32 total_sample_count = 0;
+
+ _fd->readByte(); // version
+ _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags
+
+ st->stts_count = _fd->readUint32BE();
+ st->stts_data = new MOVstts[st->stts_count];
+
+ debug(0, "track[%i].stts.entries = %i", _numStreams - 1, st->stts_count);
+
+ st->time_rate = 0;
+
+ for (int32 i = 0; i < st->stts_count; i++) {
+ int sample_duration;
+ int sample_count;
+
+ sample_count = _fd->readUint32BE();
+ sample_duration = _fd->readUint32BE();
+ st->stts_data[i].count = sample_count;
+ st->stts_data[i].duration = sample_duration;
+
+ st->time_rate = ff_gcd(st->time_rate, sample_duration);
+
+ debug(0, "sample_count=%d, sample_duration=%d", sample_count, sample_duration);
+
+ duration += sample_duration * sample_count;
+ total_sample_count += sample_count;
+ }
+
+ st->nb_frames = total_sample_count;
+
+ if (duration)
+ st->duration = duration;
+
+ return 0;
+}
+
+int QTPlayer::readSTCO(MOVatom atom) {
+ MOVStreamContext *st = _streams[_numStreams - 1];
+
+ _fd->readByte(); // version
+ _fd->readByte(); _fd->readByte(); _fd->readByte(); // flags
+
+ st->chunk_count = _fd->readUint32BE();
+ st->chunk_offsets = new uint32[st->chunk_count];
+
+ if (!st->chunk_offsets)
+ return -1;
+
+ for (uint32 i = 0; i < st->chunk_count; i++) {
+ // WORKAROUND/HACK: The offsets in Riven videos (aka inside the Mohawk archives themselves)
+ // have offsets relative to the archive and not the video. This is quite nasty. We subtract
+ // the initial offset of the stream to get the correct value inside of the stream.
+ st->chunk_offsets[i] = _fd->readUint32BE() - _fd->getBeginOffset();
+ }
+
+ for (uint32 i = 0; i < _numStreams; i++) {
+ MOVStreamContext *sc2 = _streams[i];
+
+ if(sc2 && sc2->chunk_offsets){
+ uint32 first = sc2->chunk_offsets[0];
+ uint32 last = sc2->chunk_offsets[sc2->chunk_count - 1];
+
+ if(first >= st->chunk_offsets[st->chunk_count - 1] || last <= st->chunk_offsets[0])
+ _ni = 1;
+ }
+ }
+
+ return 0;
+}
+
+int QTPlayer::readWAVE(MOVatom atom) {
+ if (_numStreams < 1)
+ return 0;
+
+ MOVStreamContext *st = _streams[_numStreams - 1];
+
+ if (atom.size > (1 << 30))
+ return -1;
+
+ if (st->codec_tag == MKID_BE('QDM2')) // Read extradata for QDM2
+ st->extradata = _fd->readStream(atom.size - 8);
+ else if (atom.size > 8)
+ return readDefault(atom);
+ else
+ _fd->skip(atom.size);
+
+ return 0;
+}
+
+void QTPlayer::closeFile() {
+ for (uint32 i = 0; i < _numStreams; i++)
+ delete _streams[i];
+
+ delete _fd;
+
+ // The audio stream is deleted automatically
+ _audStream = NULL;
+}
+
+void QTPlayer::resetInternal() {
+ if (_audioStreamIndex >= 0) {
+ _curAudioChunk = 0;
+ _audStream = new QueuedAudioStream(_streams[_audioStreamIndex]->sample_rate, _streams[_audioStreamIndex]->channels);
+ }
+}
+
+Common::SeekableReadStream *QTPlayer::getNextFramePacket() {
+ if (_videoStreamIndex < 0)
+ return NULL;
+
+ // First, we have to track down which chunk holds the sample and which sample in the chunk contains the frame we are looking for.
+ int32 totalSampleCount = 0;
+ int32 sampleInChunk = 0;
+ int32 actualChunk = -1;
+
+ for (uint32 i = 0; i < _streams[_videoStreamIndex]->chunk_count; i++) {
+ int32 sampleToChunkIndex = -1;
+
+ for (uint32 j = 0; j < _streams[_videoStreamIndex]->sample_to_chunk_sz; j++)
+ if (i >= _streams[_videoStreamIndex]->sample_to_chunk[j].first - 1)
+ sampleToChunkIndex = j;
+
+ if (sampleToChunkIndex < 0)
+ error("This chunk (%d) is imaginary", sampleToChunkIndex);
+
+ totalSampleCount += _streams[_videoStreamIndex]->sample_to_chunk[sampleToChunkIndex].count;
+
+ if (totalSampleCount > getCurFrame()) {
+ actualChunk = i;
+ sampleInChunk = _streams[_videoStreamIndex]->sample_to_chunk[sampleToChunkIndex].count - totalSampleCount + getCurFrame();
+ break;
+ }
+ }
+
+ if (actualChunk < 0) {
+ warning ("Could not find data for frame %d", getCurFrame());
+ return NULL;
+ }
+
+ // Next seek to that frame
+ _fd->seek(_streams[_videoStreamIndex]->chunk_offsets[actualChunk]);
+
+ // Then, if the chunk holds more than one frame, seek to where the frame we want is located
+ for (int32 i = getCurFrame() - sampleInChunk; i < getCurFrame(); i++) {
+ if (_streams[_videoStreamIndex]->sample_size != 0)
+ _fd->skip(_streams[_videoStreamIndex]->sample_size);
+ else
+ _fd->skip(_streams[_videoStreamIndex]->sample_sizes[i]);
+ }
+
+ // Finally, read in the raw data for the frame
+ //printf ("Frame Data[%d]: Offset = %d, Size = %d\n", getCurFrame(), _fd->pos(), _streams[_videoStreamIndex]->sample_sizes[getCurFrame()]);
+
+ if (_streams[_videoStreamIndex]->sample_size != 0)
+ return _fd->readStream(_streams[_videoStreamIndex]->sample_size);
+
+ return _fd->readStream(_streams[_videoStreamIndex]->sample_sizes[getCurFrame()]);
+}
+
+bool QTPlayer::checkAudioCodecSupport(uint32 tag) {
+ // Check if the codec is a supported codec
+ if (tag == MKID_BE('twos') || tag == MKID_BE('raw ') || tag == MKID_BE('ima4') || tag == MKID_BE('QDM2'))
+ return true;
+
+ warning("Audio Codec Not Supported: \'%s\'", tag2str(tag));
+
+ return false;
+}
+
+Audio::AudioStream *QTPlayer::createAudioStream(Common::SeekableReadStream *stream) {
+ if (!stream || _audioStreamIndex < 0)
+ return NULL;
+
+ if (_streams[_audioStreamIndex]->codec_tag == MKID_BE('twos') || _streams[_audioStreamIndex]->codec_tag == MKID_BE('raw ')) {
+ // Fortunately, most of the audio used in Myst videos is raw...
+ uint16 flags = Audio::Mixer::FLAG_AUTOFREE;
+ if (_streams[_audioStreamIndex]->codec_tag == MKID_BE('raw '))
+ flags |= Audio::Mixer::FLAG_UNSIGNED;
+ if (_streams[_audioStreamIndex]->channels == 2)
+ flags |= Audio::Mixer::FLAG_STEREO;
+ if (_streams[_audioStreamIndex]->bits_per_sample == 16)
+ flags |= Audio::Mixer::FLAG_16BITS;
+ uint32 dataSize = stream->size();
+ byte *data = (byte *)malloc(dataSize);
+ stream->read(data, dataSize);
+ delete stream;
+ return Audio::makeLinearInputStream(data, dataSize, _streams[_audioStreamIndex]->sample_rate, flags, 0, 0);
+ } else if (_streams[_audioStreamIndex]->codec_tag == MKID_BE('ima4')) {
+ // Riven uses this codec (as do some Myst ME videos)
+ return Audio::makeADPCMStream(stream, true, stream->size(), Audio::kADPCMApple, _streams[_audioStreamIndex]->sample_rate, _streams[_audioStreamIndex]->channels, 34);
+ } else if (_streams[_audioStreamIndex]->codec_tag == MKID_BE('QDM2')) {
+ // Several Myst ME videos use this codec
+ return new QDM2Stream(stream, _streams[_audioStreamIndex]->extradata);
+ }
+
+ error("Unsupported audio codec");
+
+ return NULL;
+}
+
+void QTPlayer::updateAudioBuffer() {
+ if (!_audStream)
+ return;
+
+ // Keep two streams in buffer so that when the first ends, it goes right into the next
+ for (; _audStream->streamsInQueue() < 2 && _curAudioChunk < _streams[_audioStreamIndex]->chunk_count; _curAudioChunk++) {
+ Common::MemoryWriteStreamDynamic *wStream = new Common::MemoryWriteStreamDynamic();
+
+ _fd->seek(_streams[_audioStreamIndex]->chunk_offsets[_curAudioChunk]);
+
+ // First, we have to get the sample count
+ uint32 sampleCount = 0;
+ for (uint32 j = 0; j < _streams[_audioStreamIndex]->sample_to_chunk_sz; j++)
+ if (_curAudioChunk >= (_streams[_audioStreamIndex]->sample_to_chunk[j].first - 1))
+ sampleCount = _streams[_audioStreamIndex]->sample_to_chunk[j].count;
+ assert(sampleCount);
+
+ // Then calculate the right sizes
+ while (sampleCount > 0) {
+ uint32 samples = 0, size = 0;
+
+ if (_streams[_audioStreamIndex]->samples_per_frame >= 160) {
+ samples = _streams[_audioStreamIndex]->samples_per_frame;
+ size = _streams[_audioStreamIndex]->bytes_per_frame;
+ } else if (_streams[_audioStreamIndex]->samples_per_frame > 1) {
+ samples = MIN<uint32>((1024 / _streams[_audioStreamIndex]->samples_per_frame) * _streams[_audioStreamIndex]->samples_per_frame, sampleCount);
+ size = (samples / _streams[_audioStreamIndex]->samples_per_frame) * _streams[_audioStreamIndex]->bytes_per_frame;
+ } else {
+ samples = MIN<uint32>(1024, sampleCount);
+ size = samples * _streams[_audioStreamIndex]->sample_size;
+ }
+
+ // Now, we read in the data for this data and output it
+ byte *data = (byte *)malloc(size);
+ _fd->read(data, size);
+ wStream->write(data, size);
+ free(data);
+ sampleCount -= samples;
+ }
+
+ // Now queue the buffer
+ _audStream->queueAudioStream(createAudioStream(new Common::MemoryReadStream(wStream->getData(), wStream->size(), Common::DisposeAfterUse::YES)));
+ delete wStream;
+ }
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/video/qt_player.h b/engines/mohawk/video/qt_player.h
new file mode 100644
index 0000000000..48c0ec357f
--- /dev/null
+++ b/engines/mohawk/video/qt_player.h
@@ -0,0 +1,246 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+//
+// Heavily based on ffmpeg code.
+//
+// Copyright (c) 2001 Fabrice Bellard.
+// First version by Francois Revol revol@free.fr
+// Seek function by Gael Chardon gael.dev@4now.net
+//
+
+#ifndef MOHAWK_QT_PLAYER_H
+#define MOHAWK_QT_PLAYER_H
+
+#include "common/scummsys.h"
+#include "common/file.h"
+
+#include "mohawk/video/video.h"
+
+namespace Common {
+ class File;
+}
+
+namespace Mohawk {
+
+class QTPlayer : public Video {
+public:
+ QTPlayer();
+ virtual ~QTPlayer();
+
+ /**
+ * Returns the width of the video
+ * @return the width of the video
+ */
+ uint16 getWidth();
+
+ /**
+ * Returns the height of the video
+ * @return the height of the video
+ */
+ uint16 getHeight();
+
+ /**
+ * Returns the amount of frames in the video
+ * @return the amount of frames in the video
+ */
+ uint32 getFrameCount();
+
+ /**
+ * Returns the bits per pixel of the video
+ * @return the bits per pixel of the video
+ */
+ byte getBitsPerPixel();
+
+ /**
+ * Returns the codec tag of the video
+ * @return the codec tag of the video
+ */
+ uint32 getCodecTag();
+
+ /**
+ * Load a QuickTime video file from a SeekableReadStream
+ * @param stream the stream to load
+ */
+ bool loadFile(Common::SeekableReadStream* stream);
+
+ /**
+ * Get a packet of A/V data
+ */
+ //VideoPacket getNextPacket();
+
+ /**
+ * Close a QuickTime encoded video file
+ */
+ void closeFile();
+
+ ScaleMode getScaleMode();
+ byte *getPalette() { return _palette; }
+
+protected:
+ // This is the file handle from which data is read from. It can be the actual file handle or a decompressed stream.
+ Common::SeekableReadStream *_fd;
+
+ struct MOVatom {
+ uint32 type;
+ uint32 offset;
+ uint32 size;
+ };
+
+ struct ParseTable {
+ uint32 type;
+ int (QTPlayer::*func)(MOVatom atom);
+ };
+
+ struct MOVstts {
+ int count;
+ int duration;
+ };
+
+ struct MOVstsc {
+ uint32 first;
+ uint32 count;
+ uint32 id;
+ };
+
+ enum CodecType {
+ CODEC_TYPE_MOV_OTHER,
+ CODEC_TYPE_VIDEO,
+ CODEC_TYPE_AUDIO
+ };
+
+ struct MOVStreamContext {
+ MOVStreamContext() { memset(this, 0, sizeof(MOVStreamContext)); }
+ ~MOVStreamContext() {
+ delete[] chunk_offsets;
+ delete[] stts_data;
+ delete[] ctts_data;
+ delete[] sample_to_chunk;
+ delete[] sample_sizes;
+ delete[] keyframes;
+ delete extradata;
+ }
+
+ int ffindex; /* the ffmpeg stream id */
+ int is_ff_stream; /* Is this stream presented to ffmpeg ? i.e. is this an audio or video stream ? */
+ uint32 next_chunk;
+ uint32 chunk_count;
+ uint32 *chunk_offsets;
+ int stts_count;
+ MOVstts *stts_data;
+ int ctts_count;
+ MOVstts *ctts_data;
+ int edit_count; /* number of 'edit' (elst atom) */
+ uint32 sample_to_chunk_sz;
+ MOVstsc *sample_to_chunk;
+ int32 sample_to_chunk_index;
+ int sample_to_time_index;
+ uint32 sample_to_time_sample;
+ uint32 sample_to_time_time;
+ int sample_to_ctime_index;
+ int sample_to_ctime_sample;
+ uint32 sample_size;
+ uint32 sample_count;
+ uint32 *sample_sizes;
+ uint32 keyframe_count;
+ uint32 *keyframes;
+ int32 time_scale;
+ int time_rate;
+ uint32 current_sample;
+ uint32 left_in_chunk; /* how many samples before next chunk */
+
+ uint16 width;
+ uint16 height;
+ int codec_type;
+ uint32 codec_tag;
+ char codec_name[32];
+ uint16 bits_per_sample;
+ uint16 color_table_id;
+ bool palettized;
+ Common::SeekableReadStream *extradata;
+
+ uint16 stsd_version;
+ uint16 channels;
+ uint16 sample_rate;
+ uint32 samples_per_frame;
+ uint32 bytes_per_frame;
+
+ uint32 nb_frames;
+ uint32 duration;
+ uint32 start_time;
+ ScaleMode scaleMode;
+ };
+
+ const ParseTable *_parseTable;
+ bool _foundMOOV;
+ bool _foundMDAT;
+ uint32 _timeScale;
+ uint32 _duration;
+ uint32 _mdatOffset;
+ uint32 _mdatSize;
+ uint32 _next_chunk_offset;
+ MOVStreamContext *_partial;
+ uint32 _numStreams;
+ int _ni;
+ ScaleMode _scaleMode;
+ MOVStreamContext *_streams[20];
+ byte _palette[256 * 4];
+
+ void initParseTable();
+ QueuedAudioStream *getAudioStream() { return _audStream; }
+ Audio::AudioStream *createAudioStream(Common::SeekableReadStream *stream);
+ bool checkAudioCodecSupport(uint32 tag);
+ void updateAudioBuffer();
+ Common::SeekableReadStream *getNextFramePacket();
+ void resetInternal();
+ uint32 getFrameDuration(uint32 frame);
+ int8 _videoStreamIndex;
+ int8 _audioStreamIndex;
+ uint _curAudioChunk;
+ QueuedAudioStream *_audStream;
+
+ int readDefault(MOVatom atom);
+ int readLeaf(MOVatom atom);
+ int readELST(MOVatom atom);
+ int readHDLR(MOVatom atom);
+ int readMDAT(MOVatom atom);
+ int readMDHD(MOVatom atom);
+ int readMOOV(MOVatom atom);
+ int readMVHD(MOVatom atom);
+ int readTKHD(MOVatom atom);
+ int readTRAK(MOVatom atom);
+ int readSTCO(MOVatom atom);
+ int readSTSC(MOVatom atom);
+ int readSTSD(MOVatom atom);
+ int readSTSS(MOVatom atom);
+ int readSTSZ(MOVatom atom);
+ int readSTTS(MOVatom atom);
+ int readCMOV(MOVatom atom);
+ int readWAVE(MOVatom atom);
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/video/qtrle.cpp b/engines/mohawk/video/qtrle.cpp
new file mode 100644
index 0000000000..e03c2a8b7f
--- /dev/null
+++ b/engines/mohawk/video/qtrle.cpp
@@ -0,0 +1,420 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+// QuickTime RLE Decoder
+// Based off ffmpeg's QuickTime RLE decoder (written by Mike Melanson)
+
+#include "mohawk/video/qtrle.h"
+
+#include "common/scummsys.h"
+#include "common/stream.h"
+#include "common/system.h"
+#include "graphics/colormasks.h"
+#include "graphics/surface.h"
+
+namespace Mohawk {
+
+QTRLEDecoder::QTRLEDecoder(uint16 width, uint16 height, byte bitsPerPixel) : Graphics::Codec() {
+ _bitsPerPixel = bitsPerPixel;
+ _pixelFormat = g_system->getScreenFormat();
+
+ // We need to increase the surface size to a multiple of 4
+ uint16 wMod = width % 4;
+ if(wMod != 0)
+ width += 4 - wMod;
+
+ debug(2, "QTRLE corrected width: %d", width);
+
+ _surface = new Graphics::Surface();
+ _surface->create(width, height, _bitsPerPixel <= 8 ? 1 : _pixelFormat.bytesPerPixel);
+}
+
+#define CHECK_STREAM_PTR(n) \
+ if ((stream->pos() + n) > stream->size()) { \
+ warning ("Problem: stream out of bounds (%d >= %d)", stream->pos() + n, stream->size()); \
+ return; \
+ }
+
+#define CHECK_PIXEL_PTR(n) \
+ if ((int32)pixelPtr + n > _surface->w * _surface->h) { \
+ warning ("Problem: pixel ptr = %d, pixel limit = %d", pixelPtr + n, _surface->w * _surface->h); \
+ return; \
+ } \
+
+void QTRLEDecoder::decode1(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange) {
+ uint32 pixelPtr = 0;
+ byte *rgb = (byte *)_surface->pixels;
+
+ while (linesToChange) {
+ CHECK_STREAM_PTR(2);
+ byte skip = stream->readByte();
+ int8 rleCode = stream->readSByte();
+
+ if (rleCode == 0)
+ break;
+
+ if (skip & 0x80) {
+ linesToChange--;
+ rowPtr += _surface->w;
+ pixelPtr = rowPtr + 2 * (skip & 0x7f);
+ } else
+ pixelPtr += 2 * skip;
+
+ if (rleCode < 0) {
+ // decode the run length code
+ rleCode = -rleCode;
+ // get the next 2 bytes from the stream, treat them as groups of 8 pixels, and output them rleCode times */
+ CHECK_STREAM_PTR(2);
+ byte pi0 = stream->readByte();
+ byte pi1 = stream->readByte();
+ CHECK_PIXEL_PTR(rleCode * 2);
+
+ while (rleCode--) {
+ rgb[pixelPtr++] = pi0;
+ rgb[pixelPtr++] = pi1;
+ }
+ } else {
+ // copy the same pixel directly to output 2 times
+ rleCode *= 2;
+ CHECK_STREAM_PTR(rleCode);
+ CHECK_PIXEL_PTR(rleCode);
+
+ while (rleCode--)
+ rgb[pixelPtr++] = stream->readByte();
+ }
+ }
+}
+
+void QTRLEDecoder::decode2_4(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange, byte bpp) {
+ uint32 pixelPtr = 0;
+ byte *rgb = (byte *)_surface->pixels;
+ byte numPixels = (bpp == 4) ? 8 : 16;
+
+ while (linesToChange--) {
+ CHECK_STREAM_PTR(2);
+ pixelPtr = rowPtr + (numPixels * (stream->readByte() - 1));
+
+ for (int8 rleCode = stream->readSByte(); rleCode != -1; rleCode = stream->readSByte()) {
+ if (rleCode == 0) {
+ // there's another skip code in the stream
+ CHECK_STREAM_PTR(1);
+ pixelPtr += (numPixels * (stream->readByte() - 1));
+ } else if (rleCode < 0) {
+ // decode the run length code
+ rleCode = -rleCode;
+
+ // get the next 4 bytes from the stream, treat them as palette indices, and output them rleCode times */
+ CHECK_STREAM_PTR(4);
+
+ byte pi[16]; // 16 palette indices
+
+ for (int8 i = numPixels - 1; i >= 0; i--) {
+ pi[numPixels - 1 - i] = (stream->readByte() >> ((i * bpp) & 0x07)) & ((1 << bpp) - 1);
+
+ // FIXME: Is this right?
+ //stream_ptr += ((i & ((num_pixels>>2)-1)) == 0);
+ if ((i & ((numPixels >> 2) - 1)) == 0)
+ stream->readByte();
+ }
+
+ CHECK_PIXEL_PTR(rleCode * numPixels);
+
+ while (rleCode--)
+ for (byte i = 0; i < numPixels; i++)
+ rgb[pixelPtr++] = pi[i];
+ } else {
+ // copy the same pixel directly to output 4 times
+ rleCode *= 4;
+ CHECK_STREAM_PTR(rleCode);
+ CHECK_PIXEL_PTR(rleCode * (numPixels >> 2));
+
+ while (rleCode--) {
+ byte temp = stream->readByte();
+ if (bpp == 4) {
+ rgb[pixelPtr++] = (temp >> 4) & 0x0f;
+ rgb[pixelPtr++] = temp & 0x0f;
+ } else {
+ rgb[pixelPtr++] = (temp >> 6) & 0x03;
+ rgb[pixelPtr++] = (temp >> 4) & 0x03;
+ rgb[pixelPtr++] = (temp >> 2) & 0x03;
+ rgb[pixelPtr++] = temp & 0x03;
+ }
+ }
+ }
+ }
+
+ rowPtr += _surface->w;
+ }
+}
+
+void QTRLEDecoder::decode8(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange) {
+ uint32 pixelPtr = 0;
+ byte *rgb = (byte *)_surface->pixels;
+
+ while (linesToChange--) {
+ CHECK_STREAM_PTR(2);
+ pixelPtr = rowPtr + 4 * (stream->readByte() - 1);
+
+ for (int8 rleCode = stream->readSByte(); rleCode != -1; rleCode = stream->readSByte()) {
+ if (rleCode == 0) {
+ // there's another skip code in the stream
+ CHECK_STREAM_PTR(1);
+ pixelPtr += 4 * (stream->readByte() - 1);
+ } else if (rleCode < 0) {
+ // decode the run length code
+ rleCode = -rleCode;
+
+ // get the next 4 bytes from the stream, treat them as palette indices, and output them rleCode times
+ CHECK_STREAM_PTR(4);
+
+ byte pi[4]; // 4 palette indexes
+
+ for (byte i = 0; i < 4; i++)
+ pi[i] = stream->readByte();
+
+ CHECK_PIXEL_PTR(rleCode * 4);
+
+ while (rleCode--)
+ for (byte i = 0; i < 4; i++)
+ rgb[pixelPtr++] = pi[i];
+ } else {
+ // copy the same pixel directly to output 4 times
+ rleCode *= 4;
+ CHECK_STREAM_PTR(rleCode);
+ CHECK_PIXEL_PTR(rleCode);
+
+ while (rleCode--)
+ rgb[pixelPtr++] = stream->readByte();
+ }
+ }
+
+ rowPtr += _surface->w;
+ }
+}
+
+void QTRLEDecoder::decode16(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange) {
+ uint32 pixelPtr = 0;
+ OverlayColor *rgb = (OverlayColor *)_surface->pixels;
+
+ while (linesToChange--) {
+ CHECK_STREAM_PTR(2);
+ pixelPtr = rowPtr + stream->readByte() - 1;
+
+ for (int8 rleCode = stream->readSByte(); rleCode != -1; rleCode = stream->readSByte()) {
+ if (rleCode == 0) {
+ // there's another skip code in the stream
+ CHECK_STREAM_PTR(1);
+ pixelPtr += stream->readByte() - 1;
+ } else if (rleCode < 0) {
+ // decode the run length code
+ rleCode = -rleCode;
+ CHECK_STREAM_PTR(2);
+
+ uint16 rgb16 = stream->readUint16BE();
+
+ CHECK_PIXEL_PTR(rleCode);
+
+ while (rleCode--) {
+ // Convert from RGB555 to the format specified by the Overlay
+ byte r = 0, g = 0, b = 0;
+ Graphics::colorToRGB<Graphics::ColorMasks<555> >(rgb16, r, g, b);
+ rgb[pixelPtr++] = _pixelFormat.RGBToColor(r, g, b);
+ }
+ } else {
+ CHECK_STREAM_PTR(rleCode * 2);
+ CHECK_PIXEL_PTR(rleCode);
+
+ // copy pixels directly to output
+ while (rleCode--) {
+ uint16 rgb16 = stream->readUint16BE();
+
+ // Convert from RGB555 to the format specified by the Overlay
+ byte r = 0, g = 0, b = 0;
+ Graphics::colorToRGB<Graphics::ColorMasks<555> >(rgb16, r, g, b);
+ rgb[pixelPtr++] = _pixelFormat.RGBToColor(r, g, b);
+ }
+ }
+ }
+
+ rowPtr += _surface->w;
+ }
+}
+
+void QTRLEDecoder::decode24(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange) {
+ uint32 pixelPtr = 0;
+ OverlayColor *rgb = (OverlayColor *)_surface->pixels;
+
+ while (linesToChange--) {
+ CHECK_STREAM_PTR(2);
+ pixelPtr = rowPtr + stream->readByte() - 1;
+
+ for (int8 rleCode = stream->readSByte(); rleCode != -1; rleCode = stream->readSByte()) {
+ if (rleCode == 0) {
+ // there's another skip code in the stream
+ CHECK_STREAM_PTR(1);
+ pixelPtr += stream->readByte() - 1;
+ } else if (rleCode < 0) {
+ // decode the run length code
+ rleCode = -rleCode;
+
+ CHECK_STREAM_PTR(3);
+
+ byte r = stream->readByte();
+ byte g = stream->readByte();
+ byte b = stream->readByte();
+
+ CHECK_PIXEL_PTR(rleCode);
+
+ while (rleCode--)
+ rgb[pixelPtr++] = _pixelFormat.RGBToColor(r, g, b);
+ } else {
+ CHECK_STREAM_PTR(rleCode * 3);
+ CHECK_PIXEL_PTR(rleCode);
+
+ // copy pixels directly to output
+ while (rleCode--) {
+ byte r = stream->readByte();
+ byte g = stream->readByte();
+ byte b = stream->readByte();
+ rgb[pixelPtr++] = _pixelFormat.RGBToColor(r, g, b);
+ }
+ }
+ }
+
+ rowPtr += _surface->w;
+ }
+}
+
+void QTRLEDecoder::decode32(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange) {
+ uint32 pixelPtr = 0;
+ OverlayColor *rgb = (OverlayColor *)_surface->pixels;
+
+ while (linesToChange--) {
+ CHECK_STREAM_PTR(2);
+ pixelPtr = rowPtr + stream->readByte() - 1;
+
+ for (int8 rleCode = stream->readSByte(); rleCode != -1; rleCode = stream->readSByte()) {
+ if (rleCode == 0) {
+ // there's another skip code in the stream
+ CHECK_STREAM_PTR(1);
+ pixelPtr += stream->readByte() - 1;
+ } else if (rleCode < 0) {
+ // decode the run length code
+ rleCode = -rleCode;
+
+ CHECK_STREAM_PTR(4);
+
+ byte a = stream->readByte();
+ byte r = stream->readByte();
+ byte g = stream->readByte();
+ byte b = stream->readByte();
+
+ CHECK_PIXEL_PTR(rleCode);
+
+ while (rleCode--)
+ rgb[pixelPtr++] = _pixelFormat.ARGBToColor(a, r, g, b);
+ } else {
+ CHECK_STREAM_PTR(rleCode * 4);
+ CHECK_PIXEL_PTR(rleCode);
+
+ // copy pixels directly to output
+ while (rleCode--) {
+ byte a = stream->readByte();
+ byte r = stream->readByte();
+ byte g = stream->readByte();
+ byte b = stream->readByte();
+ rgb[pixelPtr++] = _pixelFormat.ARGBToColor(a, r, g, b);
+ }
+ }
+ }
+
+ rowPtr += _surface->w;
+ }
+}
+
+Graphics::Surface *QTRLEDecoder::decodeImage(Common::SeekableReadStream *stream) {
+ uint16 start_line = 0;
+ uint16 height = _surface->h;
+
+ // check if this frame is even supposed to change
+ if (stream->size() < 8)
+ return _surface;
+
+ // start after the chunk size
+ stream->readUint32BE();
+
+ // fetch the header
+ uint16 header = stream->readUint16BE();
+
+ // if a header is present, fetch additional decoding parameters
+ if (header & 8) {
+ if(stream->size() < 14)
+ return _surface;
+ start_line = stream->readUint16BE();
+ stream->readUint16BE(); // Unknown
+ height = stream->readUint16BE();
+ stream->readUint16BE(); // Unknown
+ }
+
+ uint32 row_ptr = _surface->w * start_line;
+
+ switch (_bitsPerPixel) {
+ case 1:
+ case 33:
+ decode1(stream, row_ptr, height);
+ break;
+ case 2:
+ case 34:
+ decode2_4(stream, row_ptr, height, 2);
+ break;
+ case 4:
+ case 36:
+ decode2_4(stream, row_ptr, height, 4);
+ break;
+ case 8:
+ case 40:
+ decode8(stream, row_ptr, height);
+ break;
+ case 16:
+ decode16(stream, row_ptr, height);
+ break;
+ case 24:
+ decode24(stream, row_ptr, height);
+ break;
+ case 32:
+ decode32(stream, row_ptr, height);
+ break;
+ default:
+ error ("Unsupported bits per pixel %d", _bitsPerPixel);
+ }
+
+ return _surface;
+}
+
+QTRLEDecoder::~QTRLEDecoder() {
+ _surface->free();
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/video/qtrle.h b/engines/mohawk/video/qtrle.h
new file mode 100644
index 0000000000..8bf6bac125
--- /dev/null
+++ b/engines/mohawk/video/qtrle.h
@@ -0,0 +1,57 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MOHAWK_QTRLE_H
+#define MOHAWK_QTRLE_H
+
+#include "graphics/pixelformat.h"
+#include "graphics/video/codecs/codec.h"
+
+namespace Mohawk {
+
+class QTRLEDecoder : public Graphics::Codec {
+public:
+ QTRLEDecoder(uint16 width, uint16 height, byte bitsPerPixel);
+ ~QTRLEDecoder();
+
+ Graphics::Surface *decodeImage(Common::SeekableReadStream *stream);
+
+private:
+ byte _bitsPerPixel;
+
+ Graphics::Surface *_surface;
+ Graphics::PixelFormat _pixelFormat;
+
+ void decode1(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange);
+ void decode2_4(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange, byte bpp);
+ void decode8(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange);
+ void decode16(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange);
+ void decode24(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange);
+ void decode32(Common::SeekableReadStream *stream, uint32 rowPtr, uint32 linesToChange);
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/video/rpza.cpp b/engines/mohawk/video/rpza.cpp
new file mode 100644
index 0000000000..937a6066fb
--- /dev/null
+++ b/engines/mohawk/video/rpza.cpp
@@ -0,0 +1,208 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+ // Based off ffmpeg's RPZA decoder
+
+#include "mohawk/video/rpza.h"
+
+#include "common/system.h"
+#include "graphics/colormasks.h"
+
+namespace Mohawk {
+
+RPZADecoder::RPZADecoder(uint16 width, uint16 height) : Graphics::Codec() {
+ _pixelFormat = g_system->getScreenFormat();
+
+ // We need to increase the surface size to a multiple of 4
+ uint16 wMod = width % 4;
+ if(wMod != 0)
+ width += 4 - wMod;
+
+ debug(2, "RPZA corrected width: %d", width);
+
+ _surface = new Graphics::Surface();
+ _surface->create(width, height, _pixelFormat.bytesPerPixel);
+}
+
+#define ADVANCE_BLOCK() \
+ pixelPtr += 4; \
+ if (pixelPtr >= _surface->w) { \
+ pixelPtr = 0; \
+ rowPtr += _surface->w * 4; \
+ } \
+ totalBlocks--; \
+ if (totalBlocks < 0) \
+ error("block counter just went negative (this should not happen)") \
+
+// Convert from RGB555 to the format specified by the screen
+#define PUT_PIXEL(color) \
+ if ((int32)blockPtr < _surface->w * _surface->h) { \
+ byte r = 0, g = 0, b = 0; \
+ Graphics::colorToRGB<Graphics::ColorMasks<555> >(color, r, g, b); \
+ if (_pixelFormat.bytesPerPixel == 2) \
+ *((uint16 *)_surface->pixels + blockPtr) = _pixelFormat.RGBToColor(r, g, b); \
+ else \
+ *((uint32 *)_surface->pixels + blockPtr) = _pixelFormat.RGBToColor(r, g, b); \
+ } \
+ blockPtr++
+
+Graphics::Surface *RPZADecoder::decodeImage(Common::SeekableReadStream *stream) {
+ uint16 colorA = 0, colorB = 0;
+ uint16 color4[4];
+
+ uint32 rowPtr = 0;
+ uint32 pixelPtr = 0;
+ uint32 blockPtr = 0;
+ uint32 rowInc = _surface->w - 4;
+ uint16 ta;
+ uint16 tb;
+
+ // First byte is always 0xe1. Warn if it's different
+ byte firstByte = stream->readByte();
+ if (firstByte != 0xe1)
+ warning("First RPZA chunk byte is 0x%02x instead of 0xe1", firstByte);
+
+ // Get chunk size, ingnoring first byte
+ uint32 chunkSize = stream->readUint16BE() << 8;
+ chunkSize += stream->readByte();
+
+ // If length mismatch use size from MOV file and try to decode anyway
+ if (chunkSize != (uint32)stream->size()) {
+ warning("MOV chunk size != encoded chunk size; using MOV chunk size");
+ chunkSize = stream->size();
+ }
+
+ // Number of 4x4 blocks in frame
+ int32 totalBlocks = ((_surface->w + 3) / 4) * ((_surface->h + 3) / 4);
+
+ // Process chunk data
+ while ((uint32)stream->pos() < chunkSize) {
+ byte opcode = stream->readByte(); // Get opcode
+ byte numBlocks = (opcode & 0x1f) + 1; // Extract block counter from opcode
+
+ // If opcode MSbit is 0, we need more data to decide what to do
+ if ((opcode & 0x80) == 0) {
+ colorA = (opcode << 8) | stream->readByte();
+ opcode = 0;
+ if (stream->readByte() & 0x80) {
+ // Must behave as opcode 110xxxxx, using colorA computed
+ // above. Use fake opcode 0x20 to enter switch block at
+ // the right place
+ opcode = 0x20;
+ numBlocks = 1;
+ }
+ stream->seek(-1, SEEK_CUR);
+ }
+
+ switch (opcode & 0xe0) {
+ case 0x80: // Skip blocks
+ while (numBlocks--) {
+ ADVANCE_BLOCK();
+ }
+ break;
+ case 0xa0: // Fill blocks with one color
+ colorA = stream->readUint16BE();
+ while (numBlocks--) {
+ blockPtr = rowPtr + pixelPtr;
+ for (byte pixel_y = 0; pixel_y < 4; pixel_y++) {
+ for (byte pixel_x = 0; pixel_x < 4; pixel_x++) {
+ PUT_PIXEL(colorA);
+ }
+ blockPtr += rowInc;
+ }
+ ADVANCE_BLOCK();
+ }
+ break;
+
+ // Fill blocks with 4 colors
+ case 0xc0:
+ colorA = stream->readUint16BE();
+ case 0x20:
+ colorB = stream->readUint16BE();
+
+ // Sort out the colors
+ color4[0] = colorB;
+ color4[1] = 0;
+ color4[2] = 0;
+ color4[3] = colorA;
+
+ // Red components
+ ta = (colorA >> 10) & 0x1F;
+ tb = (colorB >> 10) & 0x1F;
+ color4[1] |= ((11 * ta + 21 * tb) >> 5) << 10;
+ color4[2] |= ((21 * ta + 11 * tb) >> 5) << 10;
+
+ // Green components
+ ta = (colorA >> 5) & 0x1F;
+ tb = (colorB >> 5) & 0x1F;
+ color4[1] |= ((11 * ta + 21 * tb) >> 5) << 5;
+ color4[2] |= ((21 * ta + 11 * tb) >> 5) << 5;
+
+ // Blue components
+ ta = colorA & 0x1F;
+ tb = colorB & 0x1F;
+ color4[1] |= ((11 * ta + 21 * tb) >> 5);
+ color4[2] |= ((21 * ta + 11 * tb) >> 5);
+
+ while (numBlocks--) {
+ blockPtr = rowPtr + pixelPtr;
+ for (byte pixel_y = 0; pixel_y < 4; pixel_y++) {
+ byte index = stream->readByte();
+ for (byte pixel_x = 0; pixel_x < 4; pixel_x++){
+ byte idx = (index >> (2 * (3 - pixel_x))) & 0x03;
+ PUT_PIXEL(color4[idx]);
+ }
+ blockPtr += rowInc;
+ }
+ ADVANCE_BLOCK();
+ }
+ break;
+
+ // Fill block with 16 colors
+ case 0x00:
+ blockPtr = rowPtr + pixelPtr;
+ for (byte pixel_y = 0; pixel_y < 4; pixel_y++) {
+ for (byte pixel_x = 0; pixel_x < 4; pixel_x++){
+ // We already have color of upper left pixel
+ if (pixel_y != 0 || pixel_x != 0)
+ colorA = stream->readUint16BE();
+
+ PUT_PIXEL(colorA);
+ }
+ blockPtr += rowInc;
+ }
+ ADVANCE_BLOCK();
+ break;
+
+ // Unknown opcode
+ default:
+ error("Unknown opcode %02x in rpza chunk", opcode);
+ }
+ }
+
+ return _surface;
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/video/rpza.h b/engines/mohawk/video/rpza.h
new file mode 100644
index 0000000000..bba744cc38
--- /dev/null
+++ b/engines/mohawk/video/rpza.h
@@ -0,0 +1,48 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MOHAWK_RPZA_H
+#define MOHAWK_RPZA_H
+
+#include "graphics/pixelformat.h"
+#include "graphics/video/codecs/codec.h"
+
+namespace Mohawk {
+
+class RPZADecoder : public Graphics::Codec {
+public:
+ RPZADecoder(uint16 width, uint16 height);
+ ~RPZADecoder() { delete _surface; }
+
+ Graphics::Surface *decodeImage(Common::SeekableReadStream *stream);
+
+private:
+ Graphics::Surface *_surface;
+ Graphics::PixelFormat _pixelFormat;
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/video/smc.cpp b/engines/mohawk/video/smc.cpp
new file mode 100644
index 0000000000..c646d5be21
--- /dev/null
+++ b/engines/mohawk/video/smc.cpp
@@ -0,0 +1,385 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+// Based off ffmpeg's SMC decoder
+
+#include "mohawk/video/smc.h"
+
+namespace Mohawk {
+
+#define GET_BLOCK_COUNT() \
+ (opcode & 0x10) ? (1 + stream->readByte()) : 1 + (opcode & 0x0F);
+
+#define ADVANCE_BLOCK() \
+{ \
+ pixelPtr += 4; \
+ if (pixelPtr >= _surface->w) { \
+ pixelPtr = 0; \
+ rowPtr += _surface->w * 4; \
+ } \
+ totalBlocks--; \
+ if (totalBlocks < 0) { \
+ warning("block counter just went negative (this should not happen)"); \
+ return _surface; \
+ } \
+}
+
+SMCDecoder::SMCDecoder(uint16 width, uint16 height) {
+ _surface = new Graphics::Surface();
+ _surface->create(width, height, 1);
+}
+
+Graphics::Surface *SMCDecoder::decodeImage(Common::SeekableReadStream *stream) {
+ byte *pixels = (byte *)_surface->pixels;
+
+ uint32 numBlocks = 0;
+ uint32 colorFlags = 0;
+ uint32 colorFlagsA = 0;
+ uint32 colorFlagsB = 0;
+
+ const uint16 rowInc = _surface->w - 4;
+ int32 rowPtr = 0;
+ int32 pixelPtr = 0;
+ uint32 blockPtr = 0;
+ uint32 prevBlockPtr = 0;
+ uint32 prevBlockPtr1 = 0, prevBlockPtr2 = 0;
+ byte prevBlockFlag = false;
+ byte pixel = 0;
+
+ uint32 colorPairIndex = 0;
+ uint32 colorQuadIndex = 0;
+ uint32 colorOctetIndex = 0;
+ uint32 colorTableIndex = 0; // indices to color pair, quad, or octet tables
+
+ int32 chunkSize = stream->readUint32BE() & 0x00FFFFFF;
+ if (chunkSize != stream->size())
+ warning("MOV chunk size != SMC chunk size (%d != %d); ignoring SMC chunk size", chunkSize, stream->size());
+
+ int32 totalBlocks = ((_surface->w + 3) / 4) * ((_surface->h + 3) / 4);
+
+ // traverse through the blocks
+ while (totalBlocks != 0) {
+ // sanity checks
+
+ // make sure stream ptr hasn't gone out of bounds
+ if (stream->pos() > stream->size()) {
+ warning("SMC decoder just went out of bounds (stream ptr = %d, chunk size = %d)", stream->pos(), stream->size());
+ return _surface;
+ }
+
+ // make sure the row pointer hasn't gone wild
+ if (rowPtr >= _surface->w * _surface->h) {
+ warning("SMC decoder just went out of bounds (row ptr = %d, size = %d)", rowPtr, _surface->w * _surface->h);
+ return _surface;
+ }
+
+ byte opcode = stream->readByte();
+
+ switch (opcode & 0xF0) {
+ // skip n blocks
+ case 0x00:
+ case 0x10:
+ numBlocks = GET_BLOCK_COUNT();
+ while (numBlocks--) {
+ ADVANCE_BLOCK();
+ }
+ break;
+
+ // repeat last block n times
+ case 0x20:
+ case 0x30:
+ numBlocks = GET_BLOCK_COUNT();
+
+ // sanity check
+ if (rowPtr == 0 && pixelPtr == 0) {
+ warning("encountered repeat block opcode (%02X) but no blocks rendered yet", opcode & 0xF0);
+ break;
+ }
+
+ // figure out where the previous block started
+ if (pixelPtr == 0)
+ prevBlockPtr1 = (rowPtr - _surface->w * 4) + _surface->w - 4;
+ else
+ prevBlockPtr1 = rowPtr + pixelPtr - 4;
+
+ while (numBlocks--) {
+ blockPtr = rowPtr + pixelPtr;
+ prevBlockPtr = prevBlockPtr1;
+ for (byte y = 0; y < 4; y++) {
+ for (byte x = 0; x < 4; x++)
+ pixels[blockPtr++] = pixels[prevBlockPtr++];
+ blockPtr += rowInc;
+ prevBlockPtr += rowInc;
+ }
+ ADVANCE_BLOCK();
+ }
+ break;
+
+ // repeat previous pair of blocks n times
+ case 0x40:
+ case 0x50:
+ numBlocks = GET_BLOCK_COUNT();
+ numBlocks *= 2;
+
+ // sanity check
+ if (rowPtr == 0 && pixelPtr < 2 * 4) {
+ warning("encountered repeat block opcode (%02X) but not enough blocks rendered yet", opcode & 0xF0);
+ break;
+ }
+
+ // figure out where the previous 2 blocks started
+ if (pixelPtr == 0)
+ prevBlockPtr1 = (rowPtr - _surface->w * 4) + _surface->w - 4 * 2;
+ else if (pixelPtr == 4)
+ prevBlockPtr1 = (rowPtr - _surface->w * 4) + rowInc;
+ else
+ prevBlockPtr1 = rowPtr + pixelPtr - 4 * 2;
+
+ if (pixelPtr == 0)
+ prevBlockPtr2 = (rowPtr - _surface->w * 4) + rowInc;
+ else
+ prevBlockPtr2 = rowPtr + pixelPtr - 4;
+
+ prevBlockFlag = 0;
+ while (numBlocks--) {
+ blockPtr = rowPtr + pixelPtr;
+
+ if (prevBlockFlag)
+ prevBlockPtr = prevBlockPtr2;
+ else
+ prevBlockPtr = prevBlockPtr1;
+
+ prevBlockFlag = !prevBlockFlag;
+
+ for (byte y = 0; y < 4; y++) {
+ for (byte x = 0; x < 4; x++)
+ pixels[blockPtr++] = pixels[prevBlockPtr++];
+
+ blockPtr += rowInc;
+ prevBlockPtr += rowInc;
+ }
+ ADVANCE_BLOCK();
+ }
+ break;
+
+ // 1-color block encoding
+ case 0x60:
+ case 0x70:
+ numBlocks = GET_BLOCK_COUNT();
+ pixel = stream->readByte();
+
+ while (numBlocks--) {
+ blockPtr = rowPtr + pixelPtr;
+ for (byte y = 0; y < 4; y++) {
+ for (byte x = 0; x < 4; x++)
+ pixels[blockPtr++] = pixel;
+
+ blockPtr += rowInc;
+ }
+ ADVANCE_BLOCK();
+ }
+ break;
+
+ // 2-color block encoding
+ case 0x80:
+ case 0x90:
+ numBlocks = (opcode & 0x0F) + 1;
+
+ // figure out which color pair to use to paint the 2-color block
+ if ((opcode & 0xF0) == 0x80) {
+ // fetch the next 2 colors from bytestream and store in next
+ // available entry in the color pair table
+ for (byte i = 0; i < CPAIR; i++) {
+ pixel = stream->readByte();
+ colorTableIndex = CPAIR * colorPairIndex + i;
+ _colorPairs[colorTableIndex] = pixel;
+ }
+
+ // this is the base index to use for this block
+ colorTableIndex = CPAIR * colorPairIndex;
+ colorPairIndex++;
+
+ // wraparound
+ if (colorPairIndex == COLORS_PER_TABLE)
+ colorPairIndex = 0;
+ } else
+ colorTableIndex = CPAIR * stream->readByte();
+
+ while (numBlocks--) {
+ colorFlags = stream->readUint16BE();
+ uint16 flagMask = 0x8000;
+ blockPtr = rowPtr + pixelPtr;
+ for (byte y = 0; y < 4; y++) {
+ for (byte x = 0; x < 4; x++) {
+ if (colorFlags & flagMask)
+ pixel = colorTableIndex + 1;
+ else
+ pixel = colorTableIndex;
+
+ flagMask >>= 1;
+ pixels[blockPtr++] = _colorPairs[pixel];
+ }
+
+ blockPtr += rowInc;
+ }
+ ADVANCE_BLOCK();
+ }
+ break;
+
+ // 4-color block encoding
+ case 0xA0:
+ case 0xB0:
+ numBlocks = (opcode & 0x0F) + 1;
+
+ // figure out which color quad to use to paint the 4-color block
+ if ((opcode & 0xF0) == 0xA0) {
+ // fetch the next 4 colors from bytestream and store in next
+ // available entry in the color quad table
+ for (byte i = 0; i < CQUAD; i++) {
+ pixel = stream->readByte();
+ colorTableIndex = CQUAD * colorQuadIndex + i;
+ _colorQuads[colorTableIndex] = pixel;
+ }
+
+ // this is the base index to use for this block
+ colorTableIndex = CQUAD * colorQuadIndex;
+ colorQuadIndex++;
+
+ // wraparound
+ if (colorQuadIndex == COLORS_PER_TABLE)
+ colorQuadIndex = 0;
+ } else
+ colorTableIndex = CQUAD * stream->readByte();
+
+ while (numBlocks--) {
+ colorFlags = stream->readUint32BE();
+
+ // flag mask actually acts as a bit shift count here
+ byte flagMask = 30;
+ blockPtr = rowPtr + pixelPtr;
+
+ for (byte y = 0; y < 4; y++) {
+ for (byte x = 0; x < 4; x++) {
+ pixel = colorTableIndex + ((colorFlags >> flagMask) & 0x03);
+ flagMask -= 2;
+ pixels[blockPtr++] = _colorQuads[pixel];
+ }
+ blockPtr += rowInc;
+ }
+ ADVANCE_BLOCK();
+ }
+ break;
+
+ // 8-color block encoding
+ case 0xC0:
+ case 0xD0:
+ numBlocks = (opcode & 0x0F) + 1;
+
+ // figure out which color octet to use to paint the 8-color block
+ if ((opcode & 0xF0) == 0xC0) {
+ // fetch the next 8 colors from bytestream and store in next
+ // available entry in the color octet table
+ for (byte i = 0; i < COCTET; i++) {
+ pixel = stream->readByte();
+ colorTableIndex = COCTET * colorOctetIndex + i;
+ _colorOctets[colorTableIndex] = pixel;
+ }
+
+ // this is the base index to use for this block
+ colorTableIndex = COCTET * colorOctetIndex;
+ colorOctetIndex++;
+
+ // wraparound
+ if (colorOctetIndex == COLORS_PER_TABLE)
+ colorOctetIndex = 0;
+ } else
+ colorTableIndex = COCTET * stream->readByte();
+
+ while (numBlocks--) {
+ /*
+ For this input of 6 hex bytes:
+ 01 23 45 67 89 AB
+ Mangle it to this output:
+ flags_a = xx012456, flags_b = xx89A37B
+ */
+
+ // build the color flags
+ byte flagData[6];
+ stream->read(flagData, 6);
+
+ colorFlagsA = ((READ_BE_UINT16(flagData) & 0xFFF0) << 8) | (READ_BE_UINT16(flagData + 2) >> 4);
+ colorFlagsB = ((READ_BE_UINT16(flagData + 4) & 0xFFF0) << 8) | ((flagData[1] & 0xF) << 8) |
+ ((flagData[3] & 0xF) << 4) | (flagData[5] & 0xf);
+
+ colorFlags = colorFlagsA;
+
+ // flag mask actually acts as a bit shift count here
+ byte flagMask = 21;
+ blockPtr = rowPtr + pixelPtr;
+ for (byte y = 0; y < 4; y++) {
+ // reload flags at third row (iteration y == 2)
+ if (y == 2) {
+ colorFlags = colorFlagsB;
+ flagMask = 21;
+ }
+
+ for (byte x = 0; x < 4; x++) {
+ pixel = colorTableIndex + ((colorFlags >> flagMask) & 0x07);
+ flagMask -= 3;
+ pixels[blockPtr++] = _colorOctets[pixel];
+ }
+
+ blockPtr += rowInc;
+ }
+ ADVANCE_BLOCK();
+ }
+ break;
+
+ // 16-color block encoding (every pixel is a different color)
+ case 0xE0:
+ numBlocks = (opcode & 0x0F) + 1;
+
+ while (numBlocks--) {
+ blockPtr = rowPtr + pixelPtr;
+ for (byte y = 0; y < 4; y++) {
+ for (byte x = 0; x < 4; x++)
+ pixels[blockPtr++] = stream->readByte();
+
+ blockPtr += rowInc;
+ }
+ ADVANCE_BLOCK();
+ }
+ break;
+
+ case 0xF0:
+ warning("0xF0 opcode seen in SMC chunk (contact the developers)");
+ break;
+ }
+ }
+
+ return _surface;
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/video/smc.h b/engines/mohawk/video/smc.h
new file mode 100644
index 0000000000..73c11c167b
--- /dev/null
+++ b/engines/mohawk/video/smc.h
@@ -0,0 +1,58 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MOHAWK_VIDEO_SMC_H
+#define MOHAWK_VIDEO_SMC_H
+
+#include "graphics/video/codecs/codec.h"
+
+namespace Mohawk {
+
+enum {
+ CPAIR = 2,
+ CQUAD = 4,
+ COCTET = 8,
+ COLORS_PER_TABLE = 256
+};
+
+class SMCDecoder : public Graphics::Codec {
+public:
+ SMCDecoder(uint16 width, uint16 height);
+ ~SMCDecoder() { delete _surface; }
+
+ Graphics::Surface *decodeImage(Common::SeekableReadStream *stream);
+
+private:
+ Graphics::Surface *_surface;
+
+ // SMC color tables
+ byte _colorPairs[COLORS_PER_TABLE * CPAIR];
+ byte _colorQuads[COLORS_PER_TABLE * CQUAD];
+ byte _colorOctets[COLORS_PER_TABLE * COCTET];
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/video/video.cpp b/engines/mohawk/video/video.cpp
new file mode 100644
index 0000000000..1abebf75af
--- /dev/null
+++ b/engines/mohawk/video/video.cpp
@@ -0,0 +1,582 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "mohawk/file.h"
+#include "mohawk/video/video.h"
+#include "mohawk/video/qt_player.h"
+
+// Codecs
+#include "mohawk/video/cinepak.h"
+#include "mohawk/video/qtrle.h"
+#include "mohawk/video/rpza.h"
+#include "mohawk/video/smc.h"
+
+namespace Mohawk {
+
+////////////////////////////////////////////
+// QueuedAudioStream
+////////////////////////////////////////////
+
+QueuedAudioStream::QueuedAudioStream(int rate, int channels, bool autofree) {
+ _rate = rate;
+ _channels = channels;
+ _autofree = autofree;
+ _finished = false;
+}
+
+QueuedAudioStream::~QueuedAudioStream() {
+ if (_autofree)
+ while (!_queue.empty())
+ delete _queue.pop();
+ _queue.clear();
+}
+
+void QueuedAudioStream::queueAudioStream(Audio::AudioStream *audStream) {
+ if (audStream->getRate() != getRate() && audStream->isStereo() && isStereo())
+ error("QueuedAudioStream::queueAudioStream: audStream has mismatched parameters");
+
+ _queue.push(audStream);
+}
+
+int QueuedAudioStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samplesDecoded = 0;
+
+ while (samplesDecoded < numSamples && !_queue.empty()) {
+ samplesDecoded += _queue.front()->readBuffer(buffer + samplesDecoded, numSamples - samplesDecoded);
+
+ if (_queue.front()->endOfData()) {
+ Audio::AudioStream *temp = _queue.pop();
+ if (_autofree)
+ delete temp;
+ }
+ }
+
+ return samplesDecoded;
+}
+
+////////////////////////////////////////////
+// Video
+////////////////////////////////////////////
+
+Video::Video() {
+ _videoCodec = NULL;
+ _noCodecFound = false;
+ _curFrame = -1;
+ _lastFrameStart = _nextFrameStart = 0;
+ _audHandle = Audio::SoundHandle();
+}
+
+Video::~Video() {
+}
+
+void Video::stop() {
+ stopAudio();
+
+ if (!_noCodecFound)
+ delete _videoCodec;
+
+ closeFile();
+}
+
+void Video::reset() {
+ delete _videoCodec; _videoCodec = NULL;
+ _noCodecFound = false;
+ _curFrame = -1;
+ _lastFrameStart = _nextFrameStart = 0;
+
+ stopAudio();
+ resetInternal();
+ startAudio();
+}
+
+Graphics::Codec *Video::createCodec(uint32 codecTag, byte bitsPerPixel) {
+ if (codecTag == MKID_BE('cvid')) {
+ // Cinepak: As used by most Myst and all Riven videos as well as some Myst ME videos. "The Chief" videos also use this.
+ return new CinepakDecoder();
+ } else if (codecTag == MKID_BE('rpza')) {
+ // Apple Video ("Road Pizza"): Used by some Myst videos.
+ return new RPZADecoder(getWidth(), getHeight());
+ } else if (codecTag == MKID_BE('rle ')) {
+ // QuickTime RLE: Used by some Myst ME videos.
+ return new QTRLEDecoder(getWidth(), getHeight(), bitsPerPixel);
+ } else if (codecTag == MKID_BE('smc ')) {
+ // Apple SMC: Used by some Myst videos.
+ return new SMCDecoder(getWidth(), getHeight());
+ } else if (codecTag == MKID_BE('SVQ1')) {
+ // Sorenson Video 1: Used by some Myst ME videos.
+ warning ("Sorenson Video 1 not yet supported");
+ } else if (codecTag == MKID_BE('SVQ3')) {
+ // Sorenson Video 3: Used by some Myst ME videos.
+ warning ("Sorenson Video 3 not yet supported");
+ } else if (codecTag == MKID_BE('jpeg')) {
+ // Motion JPEG: Used by some Myst ME videos.
+ warning ("Motion JPEG not yet supported");
+ } else if (codecTag == MKID_BE('QkBk')) {
+ // CDToons: Used by most of the Broderbund games. This is an unknown format so far.
+ warning ("CDToons not yet supported");
+ } else {
+ warning ("Unsupported codec \'%s\'", tag2str(codecTag));
+ }
+
+ return NULL;
+}
+
+void Video::startAudio() {
+ Audio::AudioStream *audStream = getAudioStream();
+
+ if (!audStream) // No audio/audio not supported
+ return;
+
+ g_system->getMixer()->playInputStream(Audio::Mixer::kPlainSoundType, &_audHandle, audStream);
+}
+
+void Video::pauseAudio() {
+ g_system->getMixer()->pauseHandle(_audHandle, true);
+}
+
+void Video::resumeAudio() {
+ g_system->getMixer()->pauseHandle(_audHandle, false);
+}
+
+void Video::stopAudio() {
+ g_system->getMixer()->stopHandle(_audHandle);
+}
+
+Graphics::Surface *Video::getNextFrame() {
+ if (_noCodecFound || _curFrame >= (int32)getFrameCount() - 1)
+ return NULL;
+
+ if (_nextFrameStart == 0)
+ _nextFrameStart = g_system->getMillis() * 100;
+
+ _lastFrameStart = _nextFrameStart;
+ _curFrame++;
+ _nextFrameStart = getFrameDuration() + _lastFrameStart;
+
+ Common::SeekableReadStream *frameData = getNextFramePacket();
+
+ if (!_videoCodec) {
+ _videoCodec = createCodec(getCodecTag(), getBitsPerPixel());
+ // If we don't get it still, the codec is unsupported ;)
+ if (!_videoCodec) {
+ _noCodecFound = true;
+ return NULL;
+ }
+ }
+
+ if (frameData) {
+ Graphics::Surface *frame = _videoCodec->decodeImage(frameData);
+ delete frameData;
+ return frame;
+ }
+
+ return NULL;
+}
+
+bool Video::endOfVideo() {
+ QueuedAudioStream *audStream = getAudioStream();
+
+ return (!audStream || audStream->endOfData()) && (_noCodecFound || _curFrame >= (int32)getFrameCount() - 1);
+}
+
+bool Video::needsUpdate() {
+ if (endOfVideo())
+ return false;
+
+ if (_curFrame == -1)
+ return true;
+
+ return (g_system->getMillis() * 100 - _lastFrameStart) >= getFrameDuration();
+}
+
+////////////////////////////////////////////
+// VideoManager
+////////////////////////////////////////////
+
+VideoManager::VideoManager(MohawkEngine* vm) : _vm(vm) {
+ if (_vm->getFeatures() & GF_HASBINK) {
+ warning ("This game uses bink video. Playback is disabled.");
+ _videoType = kBinkVideo;
+ } else {
+ _videoType = kQuickTimeVideo;
+ }
+
+ _pauseStart = 0;
+}
+
+VideoManager::~VideoManager() {
+ _mlstRecords.clear();
+ stopVideos();
+}
+
+void VideoManager::pauseVideos() {
+ for (uint16 i = 0; i < _videoStreams.size(); i++)
+ _videoStreams[i]->pauseAudio();
+ _pauseStart = _vm->_system->getMillis() * 100;
+}
+
+void VideoManager::resumeVideos() {
+ for (uint16 i = 0; i < _videoStreams.size(); i++) {
+ _videoStreams[i]->addPauseTime(_vm->_system->getMillis() * 100 - _pauseStart);
+ _videoStreams[i]->resumeAudio();
+ }
+
+ _pauseStart = 0;
+}
+
+void VideoManager::stopVideos() {
+ for (uint16 i = 0; i < _videoStreams.size(); i++)
+ _videoStreams[i]->stop();
+ _videoStreams.clear();
+}
+
+void VideoManager::playMovie(Common::String filename, uint16 x, uint16 y, bool clearScreen) {
+ Common::File *file = new Common::File();
+ if (!file->open(filename))
+ return; // Return silently for now...
+
+ VideoEntry entry;
+ entry.video = createVideo();
+
+ if (!entry.video)
+ return;
+
+ entry->loadFile(file);
+
+ // Clear screen if requested
+ if (clearScreen) {
+ _vm->_system->fillScreen(_vm->_system->getScreenFormat().RGBToColor(0, 0, 0));
+ _vm->_system->updateScreen();
+ }
+
+ entry.x = x;
+ entry.y = y;
+ entry.loop = false;
+ playMovie(entry);
+}
+
+void VideoManager::playMovieCentered(Common::String filename, bool clearScreen) {
+ Common::File *file = new Common::File();
+ if (!file->open(filename))
+ return; // Return silently for now...
+
+ VideoEntry entry;
+ entry.video = createVideo();
+
+ if (!entry.video)
+ return;
+
+ entry->loadFile(file);
+
+ // Clear screen if requested
+ if (clearScreen) {
+ _vm->_system->fillScreen(_vm->_system->getScreenFormat().RGBToColor(0, 0, 0));
+ _vm->_system->updateScreen();
+ }
+
+ entry.x = (_vm->_system->getWidth() - entry->getWidth()) / 2;
+ entry.y = (_vm->_system->getHeight() - entry->getHeight()) / 2;
+ entry.loop = false;
+ playMovie(entry);
+}
+
+void VideoManager::playMovie(VideoEntry videoEntry) {
+ // Add video to the list
+ _videoStreams.push_back(videoEntry);
+
+ bool continuePlaying = true;
+ videoEntry->startAudio();
+
+ while (!videoEntry->endOfVideo() && !_vm->shouldQuit() && continuePlaying) {
+ if (updateBackgroundMovies())
+ _vm->_system->updateScreen();
+
+ Common::Event event;
+ while (_vm->_system->getEventManager()->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_RTL:
+ case Common::EVENT_QUIT:
+ continuePlaying = false;
+ break;
+ case Common::EVENT_KEYDOWN:
+ switch (event.kbd.keycode) {
+ case Common::KEYCODE_SPACE:
+ _vm->pauseGame();
+ break;
+ case Common::KEYCODE_ESCAPE:
+ continuePlaying = false;
+ break;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ // Cut down on CPU usage
+ _vm->_system->delayMillis(10);
+ }
+
+ videoEntry->stop();
+
+ _videoStreams.clear();
+}
+
+void VideoManager::playBackgroundMovie(Common::String filename, int16 x, int16 y, bool loop) {
+ Common::File *file = new Common::File();
+ if (!file->open(filename))
+ return; // Return silently for now...
+
+ VideoEntry entry;
+ entry.video = createVideo();
+
+ if (!entry.video)
+ return;
+
+ entry->loadFile(file);
+
+ // Center x if requested
+ if (x < 0)
+ x = (_vm->_system->getWidth() - entry->getWidth()) / 2;
+
+ // Center y if requested
+ if (y < 0)
+ y = (_vm->_system->getHeight() - entry->getHeight()) / 2;
+
+ entry.x = x;
+ entry.y = y;
+ entry.loop = loop;
+
+ entry->startAudio();
+ _videoStreams.push_back(entry);
+}
+
+bool VideoManager::updateBackgroundMovies() {
+ bool updateScreen = false;
+
+ for (uint32 i = 0; i < _videoStreams.size() && !_vm->shouldQuit(); i++) {
+ // Remove any videos that are over
+ if (_videoStreams[i]->endOfVideo()) {
+ if (_videoStreams[i].loop) {
+ _videoStreams[i]->reset();
+ } else {
+ delete _videoStreams[i].video;
+ _videoStreams.remove_at(i);
+ i--;
+ continue;
+ }
+ }
+
+ // Check if we need to draw a frame
+ if (_videoStreams[i]->needsUpdate()) {
+ Graphics::Surface *frame = _videoStreams[i]->getNextFrame();
+ bool deleteFrame = false;
+
+ if (frame) {
+ // Convert from 8bpp to the current screen format if necessary
+ if (frame->bytesPerPixel == 1) {
+ Graphics::Surface *newFrame = new Graphics::Surface();
+ Graphics::PixelFormat pixelFormat = _vm->_system->getScreenFormat();
+ byte *palette = _videoStreams[i]->getPalette();
+ assert(palette);
+
+ newFrame->create(frame->w, frame->h, pixelFormat.bytesPerPixel);
+
+ for (uint16 j = 0; j < frame->h; j++) {
+ for (uint16 k = 0; k < frame->w; k++) {
+ byte palIndex = *((byte *)frame->getBasePtr(k, j));
+ byte r = palette[palIndex * 4];
+ byte g = palette[palIndex * 4 + 1];
+ byte b = palette[palIndex * 4 + 2];
+ if (pixelFormat.bytesPerPixel == 2)
+ *((uint16 *)newFrame->getBasePtr(k, j)) = pixelFormat.RGBToColor(r, g, b);
+ else
+ *((uint32 *)newFrame->getBasePtr(k, j)) = pixelFormat.RGBToColor(r, g, b);
+ }
+ }
+
+ frame = newFrame;
+ deleteFrame = true;
+ }
+
+ // Check if we're drawing at a 2x or 4x resolution (because of
+ // evil QuickTime scaling it first).
+ if (_videoStreams[i]->getScaleMode() == kScaleHalf || _videoStreams[i]->getScaleMode() == kScaleQuarter) {
+ byte scaleFactor = (_videoStreams[i]->getScaleMode() == kScaleHalf) ? 2 : 4;
+
+ Graphics::Surface scaledSurf;
+ scaledSurf.create(_videoStreams[i]->getWidth() / scaleFactor, _videoStreams[i]->getHeight() / scaleFactor, frame->bytesPerPixel);
+
+ for (uint32 j = 0; j < scaledSurf.h; j++)
+ for (uint32 k = 0; k < scaledSurf.w; k++)
+ memcpy(scaledSurf.getBasePtr(k, j), frame->getBasePtr(k * scaleFactor, j * scaleFactor), frame->bytesPerPixel);
+
+ _vm->_system->copyRectToScreen((byte*)scaledSurf.pixels, scaledSurf.pitch, _videoStreams[i].x, _videoStreams[i].y, scaledSurf.w, scaledSurf.h);
+ scaledSurf.free();
+ } else {
+ // Clip the width/height to make sure we stay on the screen (Myst does this a few times)
+ uint16 width = MIN<int32>(_videoStreams[i]->getWidth(), _vm->_system->getWidth() - _videoStreams[i].x);
+ uint16 height = MIN<int32>(_videoStreams[i]->getHeight(), _vm->_system->getHeight() - _videoStreams[i].y);
+ _vm->_system->copyRectToScreen((byte*)frame->pixels, frame->pitch, _videoStreams[i].x, _videoStreams[i].y, width, height);
+ }
+
+ // We've drawn something to the screen, make sure we update it
+ updateScreen = true;
+
+ // Delete the frame if we're using the buffer from the 8bpp conversion
+ if (deleteFrame) {
+ frame->free();
+ delete frame;
+ }
+ }
+ }
+
+ // Update the audio buffer too
+ _videoStreams[i]->updateAudioBuffer();
+ }
+
+ // Return true if we need to update the screen
+ return updateScreen;
+}
+
+void VideoManager::activateMLST(uint16 mlstId, uint16 card) {
+ Common::SeekableReadStream *mlstStream = _vm->getRawData(ID_MLST, card);
+ uint16 recordCount = mlstStream->readUint16BE();
+
+ for (uint16 i = 0; i < recordCount; i++) {
+ MLSTRecord mlstRecord;
+ mlstRecord.index = mlstStream->readUint16BE();
+ mlstRecord.movieID = mlstStream->readUint16BE();
+ mlstRecord.code = mlstStream->readUint16BE();
+ mlstRecord.left = mlstStream->readUint16BE();
+ mlstRecord.top = mlstStream->readUint16BE();
+
+ for (byte j = 0; j < 2; j++)
+ if (mlstStream->readUint16BE() != 0)
+ warning("u0[%d] in MLST non-zero", j);
+
+ if (mlstStream->readUint16BE() != 0xFFFF)
+ warning("u0[2] in MLST not 0xFFFF");
+
+ mlstRecord.loop = mlstStream->readUint16BE();
+ mlstRecord.volume = mlstStream->readUint16BE();
+ mlstRecord.u1 = mlstStream->readUint16BE();
+
+ if (mlstRecord.u1 != 1)
+ warning("mlstRecord.u1 not 1");
+
+ // Enable the record by default
+ mlstRecord.enabled = true;
+
+ if (mlstRecord.index == mlstId) {
+ _mlstRecords.push_back(mlstRecord);
+ break;
+ }
+ }
+
+ delete mlstStream;
+}
+
+void VideoManager::playMovie(uint16 id) {
+ for (uint16 i = 0; i < _mlstRecords.size(); i++)
+ if (_mlstRecords[i].code == id) {
+ warning("STUB: Play tMOV %d (non-blocking) at (%d, %d)", _mlstRecords[i].movieID, _mlstRecords[i].left, _mlstRecords[i].top);
+ return; // TODO: This will do a lot of things wrong if enabled right now ;)
+ VideoEntry entry;
+ entry.video = new QTPlayer();
+ entry->loadFile(_vm->getRawData(ID_TMOV, _mlstRecords[i].movieID));
+ entry.x = _mlstRecords[i].left;
+ entry.y = _mlstRecords[i].top;
+ entry.id = _mlstRecords[i].movieID;
+ entry.loop = _mlstRecords[i].loop != 0;
+ _videoStreams.push_back(entry);
+ return;
+ }
+}
+
+void VideoManager::playMovieBlocking(uint16 id) {
+ // NOTE/TODO: playMovieBlocking can be called after playMovie, essentially
+ // making it just a playMovieBlocking. It basically nullifies the first call.
+
+ for (uint16 i = 0; i < _mlstRecords.size(); i++)
+ if (_mlstRecords[i].code == id) {
+ warning("STUB: Play tMOV %d (blocking) at (%d, %d)", _mlstRecords[i].movieID, _mlstRecords[i].left, _mlstRecords[i].top);
+
+ // TODO: See if a non-blocking movie has been activated with the same id,
+ // and if so, block input until that movie is finished.
+
+ VideoEntry entry;
+ entry.video = new QTPlayer();
+ entry->loadFile(_vm->getRawData(ID_TMOV, _mlstRecords[i].movieID));
+ entry.x = _mlstRecords[i].left;
+ entry.y = _mlstRecords[i].top;
+ entry.id = _mlstRecords[i].movieID;
+ entry.loop = false;
+ playMovie(entry);
+ return;
+ }
+}
+
+void VideoManager::stopMovie(uint16 id) {
+ for (uint16 i = 0; i < _mlstRecords.size(); i++)
+ if (_mlstRecords[i].code == id) {
+ warning("STUB: Stop tMOV %d", _mlstRecords[i].movieID);
+ return;
+ }
+}
+
+void VideoManager::enableMovie(uint16 id) {
+ debug(2, "Enabling movie %d", id);
+ for (uint16 i = 0; i < _mlstRecords.size(); i++)
+ if (_mlstRecords[i].code == id) {
+ _mlstRecords[i].enabled = true;
+ return;
+ }
+}
+
+void VideoManager::disableMovie(uint16 id) {
+ debug(2, "Disabling movie %d", id);
+ for (uint16 i = 0; i < _mlstRecords.size(); i++)
+ if (_mlstRecords[i].code == id) {
+ _mlstRecords[i].enabled = false;
+ return;
+ }
+}
+
+void VideoManager::disableAllMovies() {
+ debug(2, "Disabling all movies");
+ for (uint16 i = 0; i < _mlstRecords.size(); i++)
+ _mlstRecords[i].enabled = false;
+}
+
+Video *VideoManager::createVideo() {
+ if (_videoType == kBinkVideo || _vm->shouldQuit())
+ return NULL;
+
+ return new QTPlayer();
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/video/video.h b/engines/mohawk/video/video.h
new file mode 100644
index 0000000000..666873b9a6
--- /dev/null
+++ b/engines/mohawk/video/video.h
@@ -0,0 +1,188 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef MOHAWK_VIDEO_H
+#define MOHAWK_VIDEO_H
+
+#include "common/queue.h"
+#include "sound/audiostream.h"
+#include "sound/mixer.h"
+#include "graphics/pixelformat.h"
+#include "graphics/video/codecs/codec.h"
+
+namespace Mohawk {
+
+class MohawkEngine;
+
+struct MLSTRecord {
+ uint16 index;
+ uint16 movieID;
+ uint16 code;
+ uint16 left;
+ uint16 top;
+ uint16 u0[3];
+ uint16 loop;
+ uint16 volume;
+ uint16 u1;
+
+ bool enabled;
+};
+
+class QueuedAudioStream : public Audio::AudioStream {
+public:
+ QueuedAudioStream(int rate, int channels, bool autofree = true);
+ ~QueuedAudioStream();
+
+ int readBuffer(int16 *buffer, const int numSamples);
+ bool isStereo() const { return _channels == 2; }
+ int getRate() const { return _rate; }
+ bool endOfData() const { return _queue.empty(); }
+ bool endOfStream() const { return _finished; }
+
+ void queueAudioStream(Audio::AudioStream *audStream);
+ void finish() { _finished = true; }
+
+ uint32 streamsInQueue() { return _queue.size(); }
+
+private:
+ bool _autofree;
+ bool _finished;
+ int _rate;
+ int _channels;
+
+ Common::Queue<Audio::AudioStream*> _queue;
+};
+
+enum ScaleMode {
+ kScaleNormal = 1,
+ kScaleHalf = 2,
+ kScaleQuarter = 4
+};
+
+class Video {
+public:
+ Video();
+ virtual ~Video();
+
+ virtual bool loadFile(Common::SeekableReadStream *stream) = 0;
+ virtual void closeFile() = 0;
+ void stop();
+ void reset();
+
+ Graphics::Surface *getNextFrame();
+ virtual uint16 getWidth() = 0;
+ virtual uint16 getHeight() = 0;
+ virtual uint32 getFrameCount() = 0;
+
+ virtual void updateAudioBuffer() {}
+ void startAudio();
+ void stopAudio();
+ void pauseAudio();
+ void resumeAudio();
+
+ int32 getCurFrame() { return _curFrame; }
+ void addPauseTime(uint32 p) { _lastFrameStart += p; _nextFrameStart += p; }
+ bool needsUpdate();
+ bool endOfVideo();
+
+ virtual byte getBitsPerPixel() = 0;
+ virtual byte *getPalette() { return NULL; }
+ virtual uint32 getCodecTag() = 0;
+ virtual ScaleMode getScaleMode() { return kScaleNormal; }
+
+private:
+ Graphics::Codec *_videoCodec;
+ bool _noCodecFound;
+ Graphics::Codec *createCodec(uint32 codecTag, byte bitsPerPixel);
+ int32 _curFrame;
+ uint32 _lastFrameStart, _nextFrameStart; // In 1/100 ms
+ Audio::SoundHandle _audHandle;
+
+ uint32 getFrameDuration() { return getFrameDuration(_curFrame); }
+
+protected:
+ virtual Common::SeekableReadStream *getNextFramePacket() = 0;
+ virtual uint32 getFrameDuration(uint32 frame) = 0;
+ virtual QueuedAudioStream *getAudioStream() = 0;
+ virtual void resetInternal() {}
+};
+
+struct VideoEntry {
+ Video *video;
+ uint16 x;
+ uint16 y;
+ bool loop;
+ Common::String filename;
+ uint16 id; // Riven only
+
+ Video *operator->() const { assert(video); return video; }
+};
+
+enum VideoType {
+ kQuickTimeVideo,
+ kBinkVideo
+};
+
+class VideoManager {
+public:
+ VideoManager(MohawkEngine *vm);
+ ~VideoManager();
+
+ // Generic movie functions
+ void playMovie(Common::String filename, uint16 x = 0, uint16 y = 0, bool clearScreen = false);
+ void playMovieCentered(Common::String filename, bool clearScreen = true);
+ void playBackgroundMovie(Common::String filename, int16 x = -1, int16 y = -1, bool loop = false);
+ bool updateBackgroundMovies();
+ void pauseVideos();
+ void resumeVideos();
+ void stopVideos();
+
+ // Riven-related functions
+ void activateMLST(uint16 mlstId, uint16 card);
+ void enableMovie(uint16 id);
+ void disableMovie(uint16 id);
+ void disableAllMovies();
+ void playMovie(uint16 id);
+ void stopMovie(uint16 id);
+ void playMovieBlocking(uint16 id);
+
+ // Riven-related variables
+ Common::Array<MLSTRecord> _mlstRecords;
+
+private:
+ MohawkEngine *_vm;
+
+ void playMovie(VideoEntry videoEntry);
+ Video *createVideo();
+
+ // Keep tabs on any videos playing
+ VideoType _videoType;
+ Common::Array<VideoEntry> _videoStreams;
+ uint32 _pauseStart;
+};
+
+} // End of namespace Mohawk
+
+#endif