/* 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]); debugC(2, kDebugLoading, "****** Loading Palette"); 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)); loadFrames(*_movieArchive->getResource(MKTAG('V', 'W', 'S', 'C'), 1024)); if (_movieArchive->hasResource(MKTAG('V', 'W', 'C', 'F'), -1)) { loadConfig(*_movieArchive->getResource(MKTAG('V', 'W', 'C', 'F'), 1024)); } else { // TODO: Source this from somewhere! _movieRect = Common::Rect(0, 0, 640, 480); _stageColor = 1; } if (_movieArchive->hasResource(MKTAG('V', 'W', 'C', 'R'), -1)) { 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) { debugC(2, kDebugLoading, "****** Loading %d CastInfos", vwci.size()); 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) { debugC(2, kDebugLoading, "****** Loading %d CASt resources", cast.size()); 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) { debugC(2, kDebugLoading, "****** Loading %d STXT resources", stxt.size()); 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) { debugC(1, kDebugLoading, "****** Preloading sprite images"); Common::HashMap::iterator bc; for (bc = _loadedBitmaps->begin(); bc != _loadedBitmaps->end(); ++bc) { if (bc->_value) { uint32 tag = bc->_value->tag; uint16 imgId = bc->_key + 1024; BitmapCast *bitmapCast = bc->_value; if (_vm->getVersion() >= 4 && bitmapCast->children.size() > 0) { imgId = bitmapCast->children[0].index; tag = bitmapCast->children[0].tag; } Image::ImageDecoder *img = NULL; Common::SeekableReadStream *pic = NULL; switch (tag) { case MKTAG('D', 'I', 'B', ' '): if (_movieArchive->hasResource(MKTAG('D', 'I', 'B', ' '), imgId)) { img = new DIBDecoder(); img->loadStream(*_movieArchive->getResource(MKTAG('D', 'I', 'B', ' '), imgId)); bitmapCast->surface = img->getSurface(); } else if (isSharedCast && _vm->getSharedDIB() != NULL && _vm->getSharedDIB()->contains(imgId)) { img = new DIBDecoder(); img->loadStream(*_vm->getSharedDIB()->getVal(imgId)); bitmapCast->surface = img->getSurface(); } break; case MKTAG('B', 'I', 'T', 'D'): 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); } break; default: warning("Unknown Bitmap Cast Tag: [%d] %s", tag, tag2str(tag)); break; } 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 && w > 0 && h > 0) { 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(); } else { 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) { debugC(1, kDebugLoading, "****** Loading frames"); uint32 size = stream.readUint32(); size -= 4; if (_vm->getVersion() == 4) { 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). } else if (_vm->getVersion() > 4) { //what data is up the top of D5 VWSC? stream.readUint32(); stream.readUint32(); uint32 blockSize = stream.readUint32() - 1; stream.readUint32(); stream.readUint32(); stream.readUint32(); stream.readUint32(); for (uint32 skip = 0; skip < blockSize * 4; skip++) stream.readByte(); //header number two... this is our actual score entry point. uint32 unk1 = stream.readUint32(); uint32 unk2 = stream.readUint32(); stream.readUint32(); uint16 unk3 = stream.readUint16(); uint16 unk4 = stream.readUint16(); uint16 unk5 = stream.readUint16(); uint16 unk6 = stream.readUint16(); warning("STUB: Score::loadFrames. unk1: %x unk2: %x unk3: %x unk4: %x unk5: %x unk6: %x", unk1, unk2, unk3, unk4, unk5, unk6); } 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 && !stream.eos()) { uint16 frameSize = stream.readUint16(); debugC(kDebugLoading, 8, "++++ score frame %d (frameSize %d) size %d", _frames.size(), frameSize, size); if (frameSize > 0) { Frame *frame = new Frame(_vm); size -= frameSize; frameSize -= 2; 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()); // str->hexdump(str->size(), 32); frame->readChannels(str); delete str; debugC(3, kDebugLoading, "Frame %d actionId: %d", _frames.size(), frame->_actionId); _frames.push_back(frame); } else { warning("zero sized frame!? exiting loop until we know what to do with the tags that follow."); size = 0; } } } void Score::loadConfig(Common::SeekableSubReadStreamEndian &stream) { debugC(1, kDebugLoading, "****** Loading Config"); /*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: debugC(3, kDebugLoading, "CastTypes id: %d BitmapCast", id); // TODO: Work out the proper tag! _loadedBitmaps->setVal(id, new BitmapCast(stream, MKTAG('B', 'I', 'T', 'D'))); _castTypes[id] = kCastBitmap; break; case kCastText: debugC(3, kDebugLoading, "CastTypes id: %d TextCast", id); _loadedText->setVal(id, new TextCast(stream)); _castTypes[id] = kCastText; break; case kCastShape: debugC(3, kDebugLoading, "CastTypes id: %d ShapeCast", id); _loadedShapes->setVal(id, new ShapeCast(stream)); _castTypes[id] = kCastShape; break; case kCastButton: debugC(3, kDebugLoading, "CastTypes id: %d ButtonCast", id); _loadedButtons->setVal(id, new ButtonCast(stream)); _castTypes[id] = kCastButton; break; default: warning("Score::loadCastDataVWCR(): Unhandled cast type: %d [%s]", castType, tag2str(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); if (_frames[i]->_sprites[j]->_buttonCast->children.size() == 1) { _frames[i]->_sprites[j]->_textCast = _vm->getSharedScore()->_loadedText->getVal(_frames[i]->_sprites[j]->_buttonCast->children[0].index); } else if (_frames[i]->_sprites[j]->_buttonCast->children.size() > 0) { warning("Cast %d has too many children!", j); } } 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.size() < 2048) stream.hexdump(stream.size()); uint32 size1, size2, size3, castType, sizeToRead; byte unk1 = 0, unk2 = 0, unk3 = 0; if (_vm->getVersion() <= 3) { size1 = stream.readUint16(); sizeToRead = size1 +16; // 16 is for bounding rects 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(); sizeToRead = size1 + 2 + 16; // 16 is for bounding rects size2 = stream.readUint32(); size3 = 0; castType = stream.readByte(); unk1 = stream.readByte(); } else if (_vm->getVersion() == 5) { castType = stream.readUint32(); size3 = stream.readUint32(); size2 = stream.readUint32(); size1 = stream.readUint32(); if (castType == 1) { if (size3 == 0) return; for (uint32 skip = 0; skip < (size1 - 4) / 4; skip++) stream.readUint32(); } sizeToRead = stream.size(); } else { error("Score::loadCastData: unsupported Director version (%d)", _vm->getVersion()); } 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(sizeToRead, 1); stream.read(data, sizeToRead); Common::MemoryReadStreamEndian castStream(data, sizeToRead, stream.isBE()); switch (castType) { case kCastBitmap: _loadedBitmaps->setVal(id, new BitmapCast(castStream, res->tag, _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())); for (uint child = 0; child < res->children.size(); child++) _loadedText->getVal(id)->children.push_back(res->children[child]); _castTypes[id] = kCastText; break; case kCastShape: _loadedShapes->setVal(id, new ShapeCast(castStream, _vm->getVersion())); for (uint child = 0; child < res->children.size(); child++) _loadedShapes->getVal(id)->children.push_back(res->children[child]); _castTypes[id] = kCastShape; break; case kCastButton: _loadedButtons->setVal(id, new ButtonCast(castStream, _vm->getVersion())); for (uint child = 0; child < res->children.size(); child++) _loadedButtons->getVal(id)->children.push_back(res->children[child]); _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 [%s]", castType, tag2str(castType)); // also don't try and read the strings... we don't know what this item is. size2 = 0; break; } free(data); if (size2 && _vm->getVersion() < 5) { 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