/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "common/system.h" #include "common/config-manager.h" #include "common/events.h" #include "common/memstream.h" #include "engines/util.h" #include "graphics/font.h" #include "graphics/palette.h" #include "graphics/macgui/macfontmanager.h" #include "graphics/macgui/macwindowmanager.h" #include "image/bmp.h" #include "director/cast.h" #include "director/score.h" #include "director/frame.h" #include "director/archive.h" #include "director/sound.h" #include "director/sprite.h" #include "director/lingo/lingo.h" namespace Director { const char *scriptTypes[] = { "MovieScript", "SpriteScript", "FrameScript", "CastScript" }; const char *scriptType2str(ScriptType scr) { if (scr < 0) return "NoneScript"; if (scr > kMaxScriptType) return ""; return scriptTypes[scr]; } Score::Score(DirectorEngine *vm) { _vm = vm; _surface = new Graphics::ManagedSurface; _trailSurface = new Graphics::ManagedSurface; _lingo = _vm->getLingo(); _soundManager = _vm->getSoundManager(); _currentMouseDownSpriteId = 0; // FIXME: TODO: Check whether the original truely does it if (_vm->getVersion() <= 3) { _lingo->executeScript(kMovieScript, 0); } _lingo->processEvent(kEventPrepareMovie, kMovieScript, 0); _movieScriptCount = 0; _labels = NULL; _font = NULL; _versionMinor = _versionMajor = 0; _currentFrameRate = 20; _castArrayStart = _castArrayEnd = 0; _currentFrame = 0; _nextFrameTime = 0; _flags = 0; _stopPlay = false; _stageColor = 0; _loadedBitmaps = new Common::HashMap(); _loadedText = new Common::HashMap(); _loadedButtons = new Common::HashMap(); _loadedShapes = new Common::HashMap(); _loadedScripts = new Common::HashMap(); } void Score::setArchive(Archive *archive) { _movieArchive = archive; if (archive->hasResource(MKTAG('M', 'C', 'N', 'M'), 0)) { _macName = archive->getName(MKTAG('M', 'C', 'N', 'M'), 0).c_str(); } else { _macName = archive->getFileName(); } if (archive->hasResource(MKTAG('V', 'W', 'L', 'B'), 1024)) { loadLabels(*archive->getResource(MKTAG('V', 'W', 'L', 'B'), 1024)); } } void Score::loadArchive() { Common::Array clutList = _movieArchive->getResourceIDList(MKTAG('C', 'L', 'U', 'T')); if (clutList.size() > 1) warning("More than one palette was found (%d)", clutList.size()); if (clutList.size() == 0) { warning("CLUT resource not found, using default Mac palette"); g_system->getPaletteManager()->setPalette(defaultPalette, 0, 256); _vm->setPalette(defaultPalette, 256); } else { Common::SeekableSubReadStreamEndian *pal = _movieArchive->getResource(MKTAG('C', 'L', 'U', 'T'), clutList[0]); loadPalette(*pal); g_system->getPaletteManager()->setPalette(_vm->getPalette(), 0, _vm->getPaletteColorCount()); } if (_movieArchive->hasResource(MKTAG('F', 'O', 'N', 'D'), -1)) { debug("Movie has fonts. Loading...."); } assert(_movieArchive->hasResource(MKTAG('V', 'W', 'S', 'C'), 1024)); assert(_movieArchive->hasResource(MKTAG('V', 'W', 'C', 'F'), 1024)); loadFrames(*_movieArchive->getResource(MKTAG('V', 'W', 'S', 'C'), 1024)); loadConfig(*_movieArchive->getResource(MKTAG('V', 'W', 'C', 'F'), 1024)); if (_vm->getVersion() < 4) { assert(_movieArchive->hasResource(MKTAG('V', 'W', 'C', 'R'), 1024)); loadCastDataVWCR(*_movieArchive->getResource(MKTAG('V', 'W', 'C', 'R'), 1024)); } if (_movieArchive->hasResource(MKTAG('V', 'W', 'A', 'C'), 1024)) { loadActions(*_movieArchive->getResource(MKTAG('V', 'W', 'A', 'C'), 1024)); } if (_movieArchive->hasResource(MKTAG('V', 'W', 'F', 'I'), 1024)) { loadFileInfo(*_movieArchive->getResource(MKTAG('V', 'W', 'F', 'I'), 1024)); } if (_movieArchive->hasResource(MKTAG('V', 'W', 'F', 'M'), 1024)) { _vm->_wm->_fontMan->clearFontMapping(); loadFontMap(*_movieArchive->getResource(MKTAG('V', 'W', 'F', 'M'), 1024)); } Common::Array vwci = _movieArchive->getResourceIDList(MKTAG('V', 'W', 'C', 'I')); if (vwci.size() > 0) { for (Common::Array::iterator iterator = vwci.begin(); iterator != vwci.end(); ++iterator) loadCastInfo(*_movieArchive->getResource(MKTAG('V', 'W', 'C', 'I'), *iterator), *iterator); } Common::Array cast = _movieArchive->getResourceIDList(MKTAG('C', 'A', 'S', 't')); if (cast.size() > 0) { for (Common::Array::iterator iterator = cast.begin(); iterator != cast.end(); ++iterator) { Common::SeekableSubReadStreamEndian *stream = _movieArchive->getResource(MKTAG('C', 'A', 'S', 't'), *iterator); Resource res = _movieArchive->getResourceDetail(MKTAG('C', 'A', 'S', 't'), *iterator); loadCastData(*stream, *iterator, &res); } } setSpriteCasts(); loadSpriteImages(false); // Try to load movie script, it sits in resource A11 if (_vm->getVersion() <= 3) { Common::Array stxt = _movieArchive->getResourceIDList(MKTAG('S','T','X','T')); if (stxt.size() > 0) { for (Common::Array::iterator iterator = stxt.begin(); iterator != stxt.end(); ++iterator) { loadScriptText(*_movieArchive->getResource(MKTAG('S','T','X','T'), *iterator)); } } } } void Score::loadSpriteImages(bool isSharedCast) { Common::HashMap::iterator bc; for (bc = _loadedBitmaps->begin(); bc != _loadedBitmaps->end(); ++bc) { if (bc->_value) { uint16 imgId = bc->_key + 1024; BitmapCast *bitmapCast = bc->_value; if (_vm->getVersion() >= 4 && bitmapCast->children.size() > 0) imgId = bitmapCast->children[0].index; Image::ImageDecoder *img = NULL; if (_movieArchive->hasResource(MKTAG('D', 'I', 'B', ' '), imgId)) { img = new DIBDecoder(); img->loadStream(*_movieArchive->getResource(MKTAG('D', 'I', 'B', ' '), imgId)); bitmapCast->surface = img->getSurface(); } if (isSharedCast && _vm->getSharedDIB() != NULL && _vm->getSharedDIB()->contains(imgId)) { img = new DIBDecoder(); img->loadStream(*_vm->getSharedDIB()->getVal(imgId)); bitmapCast->surface = img->getSurface(); } Common::SeekableReadStream *pic = NULL; if (isSharedCast) { debugC(4, kDebugImages, "Shared cast BMP: id: %d", imgId); pic = _vm->getSharedBMP()->getVal(imgId); if (pic != NULL) pic->seek(0); // TODO: this actually gets re-read every loop... we need to rewind it! } else if (_movieArchive->hasResource(MKTAG('B', 'I', 'T', 'D'), imgId)) { pic = _movieArchive->getResource(MKTAG('B', 'I', 'T', 'D'), imgId); } int w = bitmapCast->initialRect.width(), h = bitmapCast->initialRect.height(); debugC(4, kDebugImages, "id: %d, w: %d, h: %d, flags: %x, some: %x, unk1: %d, unk2: %d", imgId, w, h, bitmapCast->flags, bitmapCast->someFlaggyThing, bitmapCast->unk1, bitmapCast->unk2); if (pic != NULL && bitmapCast != NULL) { if (_vm->getVersion() < 4) { img = new BITDDecoder(w, h); } else if (_vm->getVersion() < 6) { img = new BITDDecoderV4(w, h, bitmapCast->bitsPerPixel); } else { img = new Image::BitmapDecoder(); } img->loadStream(*pic); bitmapCast->surface = img->getSurface(); } warning("Image %d not found", imgId); } } } Score::~Score() { if (_surface) _surface->free(); if (_trailSurface) _trailSurface->free(); delete _surface; delete _trailSurface; if (_movieArchive) _movieArchive->close(); delete _font; delete _labels; } void Score::loadPalette(Common::SeekableSubReadStreamEndian &stream) { uint16 steps = stream.size() / 6; uint16 index = (steps * 3) - 1; uint16 _paletteColorCount = steps; byte *_palette = new byte[index + 1]; for (uint8 i = 0; i < steps; i++) { _palette[index - 2] = stream.readByte(); stream.readByte(); _palette[index - 1] = stream.readByte(); stream.readByte(); _palette[index] = stream.readByte(); stream.readByte(); index -= 3; } _vm->setPalette(_palette, _paletteColorCount); } void Score::loadFrames(Common::SeekableSubReadStreamEndian &stream) { uint32 size = stream.readUint32(); size -= 4; if (_vm->getVersion() > 3) { uint32 unk1 = stream.readUint32(); uint32 unk2 = stream.readUint32(); uint16 unk3 = stream.readUint16(); uint16 unk4 = stream.readUint16(); uint16 unk5 = stream.readUint16(); uint16 unk6 = stream.readUint16(); size -= 16; warning("STUB: Score::loadFrames. unk1: %x unk2: %x unk3: %x unk4: %x unk5: %x unk6: %x", unk1, unk2, unk3, unk4, unk5, unk6); // Unknown, some bytes - constant (refer to contuinity). } uint16 channelSize; uint16 channelOffset; Frame *initial = new Frame(_vm); _frames.push_back(initial); // This is a representation of the channelData. It gets overridden // partically by channels, hence we keep it and read the score from left to right // // TODO Merge it with shared cast byte channelData[kChannelDataSize]; memset(channelData, 0, kChannelDataSize); while (size != 0) { uint16 frameSize = stream.readUint16(); debugC(kDebugLoading, 8, "++++ score frame %d (frameSize %d) size %d", _frames.size(), frameSize, size); size -= frameSize; frameSize -= 2; Frame *frame = new Frame(_vm); while (frameSize != 0) { if (_vm->getVersion() < 4) { channelSize = stream.readByte() * 2; channelOffset = stream.readByte() * 2; frameSize -= channelSize + 2; } else { channelSize = stream.readUint16(); channelOffset = stream.readUint16(); frameSize -= channelSize + 4; } assert(channelOffset + channelSize < kChannelDataSize); stream.read(&channelData[channelOffset], channelSize); } Common::MemoryReadStreamEndian *str = new Common::MemoryReadStreamEndian(channelData, ARRAYSIZE(channelData), stream.isBE()); //Common::hexdump(channelData, ARRAYSIZE(channelData)); frame->readChannels(str); debugC(3, kDebugLoading, "Frame %d actionId: %d", _frames.size() + 1, frame->_actionId); delete str; _frames.push_back(frame); } } void Score::loadConfig(Common::SeekableSubReadStreamEndian &stream) { /*uint16 unk1 = */ stream.readUint16(); /*ver1 = */ stream.readUint16(); _movieRect = Score::readRect(stream); _castArrayStart = stream.readUint16(); _castArrayEnd = stream.readUint16(); _currentFrameRate = stream.readByte(); stream.skip(9); _stageColor = stream.readUint16(); } void Score::readVersion(uint32 rid) { _versionMinor = rid & 0xffff; _versionMajor = rid >> 16; debug("Version: %d.%d", _versionMajor, _versionMinor); } void Score::loadCastDataVWCR(Common::SeekableSubReadStreamEndian &stream) { debugC(1, kDebugLoading, "Score::loadCastDataVWCR(). start: %d, end: %d", _castArrayStart, _castArrayEnd); for (uint16 id = _castArrayStart; id <= _castArrayEnd; id++) { byte size = stream.readByte(); if (size == 0) continue; if (debugChannelSet(5, kDebugLoading)) stream.hexdump(size); uint8 castType = stream.readByte(); switch (castType) { case kCastBitmap: _loadedBitmaps->setVal(id, new BitmapCast(stream)); _castTypes[id] = kCastBitmap; break; case kCastText: _loadedText->setVal(id, new TextCast(stream)); _castTypes[id] = kCastText; break; case kCastShape: _loadedShapes->setVal(id, new ShapeCast(stream)); _castTypes[id] = kCastShape; break; case kCastButton: _loadedButtons->setVal(id, new ButtonCast(stream)); _castTypes[id] = kCastButton; break; default: warning("Score::loadCastDataVWCR(): Unhandled cast type: %d", castType); stream.skip(size - 1); break; } } } void Score::setSpriteCasts() { // Set cast pointers to sprites for (uint16 i = 0; i < _frames.size(); i++) { for (uint16 j = 0; j < _frames[i]->_sprites.size(); j++) { uint16 castId = _frames[i]->_sprites[j]->_castId; if (_vm->getSharedScore()->_loadedBitmaps->contains(castId)) { _frames[i]->_sprites[j]->_bitmapCast = _vm->getSharedScore()->_loadedBitmaps->getVal(castId); } else if (_loadedBitmaps->contains(castId)) { _frames[i]->_sprites[j]->_bitmapCast = _loadedBitmaps->getVal(castId); } if (_vm->getSharedScore()->_loadedButtons->contains(castId)) { _frames[i]->_sprites[j]->_buttonCast = _vm->getSharedScore()->_loadedButtons->getVal(castId); } else if (_loadedButtons->contains(castId)) { _frames[i]->_sprites[j]->_buttonCast = _loadedButtons->getVal(castId); } //if (_loadedScripts->contains(castId)) // _frames[i]->_sprites[j]->_bitmapCast = _loadedBitmaps->getVal(castId); if (_vm->getSharedScore()->_loadedText->contains(castId)) { _frames[i]->_sprites[j]->_textCast = _vm->getSharedScore()->_loadedText->getVal(castId); } else if (_loadedText->contains(castId)) { _frames[i]->_sprites[j]->_textCast = _loadedText->getVal(castId); } if (_vm->getSharedScore()->_loadedShapes->contains(castId)) { _frames[i]->_sprites[j]->_shapeCast = _vm->getSharedScore()->_loadedShapes->getVal(castId); } else if (_loadedShapes->contains(castId)) { _frames[i]->_sprites[j]->_shapeCast = _loadedShapes->getVal(castId); } } } } void Score::loadCastData(Common::SeekableSubReadStreamEndian &stream, uint16 id, Resource *res) { // D4+ variant if (stream.size() == 0) return; // TODO: Determine if there really is a minimum size. // This value was too small for Shape Casts. if (stream.size() < 10) { warning("CAST data id %d is too small", id); return; } debugC(3, kDebugLoading, "CASt: id: %d", id); if (debugChannelSet(5, kDebugLoading)) stream.hexdump(stream.size()); uint32 size1, size2, size3, castType; byte unk1 = 0, unk2 = 0, unk3 = 0; if (_vm->getVersion() <= 3) { size1 = stream.readUint16(); size2 = stream.readUint32(); size3 = 0; castType = stream.readByte(); unk1 = stream.readByte(); unk2 = stream.readByte(); unk3 = stream.readByte(); } else if (_vm->getVersion() == 4) { size1 = stream.readUint16() + 2; size2 = stream.readUint32(); size3 = 0; castType = stream.readByte(); unk1 = stream.readByte(); } else { // FIXME: only the cast type and the strings are good castType = stream.readUint32(); size2 = stream.readUint32(); size3 = stream.readUint32(); size1 = stream.readUint32(); assert(size1 == 0x14); size1 = 0; } debugC(3, kDebugLoading, "CASt: id: %d type: %x size1: %d size2: %d (%x) size3: %d unk1: %d unk2: %d unk3: %d", id, castType, size1, size2, size2, size3, unk1, unk2, unk3); byte *data = (byte *)calloc(size1 + 16, 1); // 16 is for bounding rects stream.read(data, size1 + 16); Common::MemoryReadStreamEndian castStream(data, size1 + 16, stream.isBE()); switch (castType) { case kCastBitmap: _loadedBitmaps->setVal(id, new BitmapCast(castStream, _vm->getVersion())); for (uint child = 0; child < res->children.size(); child++) _loadedBitmaps->getVal(id)->children.push_back(res->children[child]); _castTypes[id] = kCastBitmap; break; case kCastText: _loadedText->setVal(id, new TextCast(castStream, _vm->getVersion())); _castTypes[id] = kCastText; break; case kCastShape: _loadedShapes->setVal(id, new ShapeCast(castStream, _vm->getVersion())); _castTypes[id] = kCastShape; break; case kCastButton: _loadedButtons->setVal(id, new ButtonCast(castStream, _vm->getVersion())); _castTypes[id] = kCastButton; break; case kCastLingoScript: _loadedScripts->setVal(id, new ScriptCast(castStream, _vm->getVersion())); _castTypes[id] = kCastLingoScript; break; default: warning("Score::loadCastData(): Unhandled cast type: %d", castType); break; } free(data); if (size2) { uint32 entryType = 0; Common::Array castStrings = loadStrings(stream, entryType, false); debugCN(4, kDebugLoading, "str(%d): '", castStrings.size()); for (uint i = 0; i < castStrings.size(); i++) { debugCN(4, kDebugLoading, "%s'", castStrings[i].c_str()); if (i != castStrings.size() - 1) debugCN(4, kDebugLoading, ", '"); } debugC(4, kDebugLoading, "'"); CastInfo *ci = new CastInfo(); if (castStrings.size() >= 5) { ci->script = castStrings[0]; ci->name = castStrings[1]; ci->directory = castStrings[2]; ci->fileName = castStrings[3]; ci->type = castStrings[4]; if (!ci->script.empty()) { // the script type here could be wrong! if (ConfMan.getBool("dump_scripts")) dumpScript(ci->script.c_str(), kCastScript, id); _lingo->addCode(ci->script.c_str(), kCastScript, id); } } _castsInfo[id] = ci; } if (size3) warning("size3: %x", size3); } void Score::loadCastInto(Sprite *sprite, int castId) { switch (_castTypes[castId]) { case kCastBitmap: sprite->_bitmapCast = _loadedBitmaps->getVal(castId); break; case kCastShape: sprite->_shapeCast = _loadedShapes->getVal(castId); break; case kCastButton: sprite->_buttonCast = _loadedButtons->getVal(castId); break; case kCastText: sprite->_textCast = _loadedText->getVal(castId); break; default: warning("Score::loadCastInto(..., %d): Unhandled castType %d", castId, _castTypes[castId]); } } Common::Rect Score::getCastMemberInitialRect(int castId) { switch (_castTypes[castId]) { case kCastBitmap: return _loadedBitmaps->getVal(castId)->initialRect; case kCastShape: return _loadedShapes->getVal(castId)->initialRect; case kCastButton: return _loadedButtons->getVal(castId)->initialRect; case kCastText: return _loadedText->getVal(castId)->initialRect; default: warning("Score::getCastMemberInitialRect(%d): Unhandled castType %d", castId, _castTypes[castId]); return Common::Rect(0, 0); } } void Score::setCastMemberModified(int castId) { switch (_castTypes[castId]) { case kCastBitmap: _loadedBitmaps->getVal(castId)->modified = 1; break; case kCastShape: _loadedShapes->getVal(castId)->modified = 1; break; case kCastButton: _loadedButtons->getVal(castId)->modified = 1; break; case kCastText: _loadedText->getVal(castId)->modified = 1; break; default: warning("Score::setCastMemberModified(%d): Unhandled castType %d", castId, _castTypes[castId]); } } void Score::loadLabels(Common::SeekableSubReadStreamEndian &stream) { _labels = new Common::SortedArray