diff options
author | Eugene Sandulenko | 2009-12-29 23:18:24 +0000 |
---|---|---|
committer | Eugene Sandulenko | 2009-12-29 23:18:24 +0000 |
commit | 0ea022d076c491d802431ee90b658d5e8c06d0e0 (patch) | |
tree | 23953ed8dbd2c1cc798b6aa9aa51df93c7041c7d /engines/mohawk | |
parent | 5f1d2a88b51af43d8903866b46a424fe556abb3c (diff) | |
download | scummvm-rg350-0ea022d076c491d802431ee90b658d5e8c06d0e0.tar.gz scummvm-rg350-0ea022d076c491d802431ee90b658d5e8c06d0e0.tar.bz2 scummvm-rg350-0ea022d076c491d802431ee90b658d5e8c06d0e0.zip |
Add Mohawk engine code. Part 1/3: main code.
svn-id: r46727
Diffstat (limited to 'engines/mohawk')
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 |