From 693d5e662595f60eed41feb5254a28bbd318094e Mon Sep 17 00:00:00 2001 From: Martin Kiewitz Date: Wed, 11 Dec 2013 08:25:23 +0100 Subject: SCI: rave support (KQ6 hires portrait lip sync) Thanks to wjp and [md5] for helping --- engines/sci/graphics/portrait.cpp | 222 +++++++++++++++++++++++++++++++++++++- engines/sci/graphics/portrait.h | 11 ++ engines/sci/resource_audio.cpp | 43 ++++---- 3 files changed, 252 insertions(+), 24 deletions(-) (limited to 'engines/sci') diff --git a/engines/sci/graphics/portrait.cpp b/engines/sci/graphics/portrait.cpp index a39bcfcd88..7217af51f7 100644 --- a/engines/sci/graphics/portrait.cpp +++ b/engines/sci/graphics/portrait.cpp @@ -40,6 +40,7 @@ Portrait::Portrait(ResourceManager *resMan, EventManager *event, GfxScreen *scre } Portrait::~Portrait() { + delete[] _lipSyncDataOffsetTable; delete[] _bitmaps; delete[] _fileData; } @@ -52,7 +53,7 @@ void Portrait::init() { // 2 bytes main height (should be the same as first bitmap header height) // 2 bytes animation count // 2 bytes unknown - // 2 bytes unknown + // 2 bytes lip sync ID count // 4 bytes paletteSize (base 1) // -> 17 bytes // paletteSize bytes paletteData @@ -82,6 +83,8 @@ void Portrait::init() { _width = READ_LE_UINT16(_fileData + 3); _height = READ_LE_UINT16(_fileData + 5); _bitmapCount = READ_LE_UINT16(_fileData + 7); + _lipSyncIDCount = READ_LE_UINT16(_fileData + 11); + _bitmaps = new PortraitBitmap[_bitmapCount]; uint16 portraitPaletteSize = READ_LE_UINT16(_fileData + 13); @@ -129,7 +132,48 @@ void Portrait::init() { } data += offsetTableSize; - // raw lip-sync data follows + // raw lip-sync ID table follows + uint32 lipSyncIDTableSize; + + lipSyncIDTableSize = READ_LE_UINT32(data); + data += 4; + assert( lipSyncIDTableSize == (_lipSyncIDCount * 4) ); + _lipSyncIDTable = data; + data += lipSyncIDTableSize; + + // raw lip-sync frame table follows + uint32 lipSyncDataTableSize; + uint32 lipSyncDataTableLastOffset; + byte lipSyncData; + uint16 lipSyncDataNr; + uint16 lipSyncCurOffset; + + lipSyncDataTableSize = READ_LE_UINT32(data); + data += 4; + assert( lipSyncDataTableSize == 0x220 ); // always this size, just a safety-check + + _lipSyncData = data; + lipSyncDataTableLastOffset = lipSyncDataTableSize - 1; + _lipSyncDataOffsetTable = new uint16[ _lipSyncIDCount ]; + + lipSyncDataNr = 0; + lipSyncCurOffset = 0; + while ( (lipSyncCurOffset < lipSyncDataTableSize) && (lipSyncDataNr < _lipSyncIDCount) ) { + // We are currently at the start of ID-frame data + _lipSyncDataOffsetTable[lipSyncDataNr] = lipSyncCurOffset; + + // Look for end of ID-frame data + lipSyncData = *data++; lipSyncCurOffset++; + while ( (lipSyncData != 0xFF) && (lipSyncCurOffset < lipSyncDataTableLastOffset) ) { + // Either terminator (0xFF) or frame-data (1 byte tick count and 1 byte bitmap ID) + data++; + lipSyncData = *data++; + lipSyncCurOffset += 2; + } + lipSyncDataNr++; + } + _lipSyncDataOffsetTableEnd = data; + // last 4 bytes seem to be garbage } void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint16 verb, uint16 cond, uint16 seq) { @@ -137,10 +181,20 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint // Now init audio and sync resource uint32 audioNumber = ((noun & 0xff) << 24) | ((verb & 0xff) << 16) | ((cond & 0xff) << 8) | (seq & 0xff); - ResourceId syncResourceId = ResourceId(kResourceTypeSync36, resourceId, noun, verb, cond, seq); - Resource *syncResource = _resMan->findResource(syncResourceId, true); + //ResourceId syncResourceId = ResourceId(kResourceTypeSync36, resourceId, noun, verb, cond, seq); + //Resource *syncResource = _resMan->findResource(syncResourceId, true); + ResourceId raveResourceId = ResourceId(kResourceTypeRave, resourceId, noun, verb, cond, seq); + Resource *raveResource = _resMan->findResource(raveResourceId, true); + +#if 0 uint syncOffset = 0; +#endif + // TODO: play through the game if this is 100% accurate + // TODO: maybe try to create the missing sync resources for low-res KQ6 out of the rave resources + + uint raveOffset = 0; + #if 0 // Dump the sync resources to disk Common::DumpFile *outFile = new Common::DumpFile(); @@ -172,7 +226,92 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint // Start playing audio... _audio->stopAudio(); _audio->startAudio(resourceId, audioNumber); + + if (!raveResource) { + warning("kPortrait: no rave resource %d %X", resourceId, audioNumber); + return; + } + + // Do animation depending on rave resource till audio is done playing + int16 raveTicks; + uint16 raveID; + byte *raveLipSyncData; + byte raveLipSyncTicks; + byte raveLipSyncBitmapNr; + int timerPosition = 0; + int timerPositionWithin = 0; + int curPosition; + SciEvent curEvent; + bool userAbort = false; + + while ((raveOffset < raveResource->size) && (!userAbort)) { + // rave string starts with tick count, followed by lipSyncID, tick count and so on + raveTicks = raveGetTicks(raveResource, &raveOffset); + if (raveTicks < 0) + break; + + // get lipSyncID + raveID = raveGetID(raveResource, &raveOffset); + if (raveID) { + raveLipSyncData = raveGetLipSyncData(raveID); + } else { + raveLipSyncData = NULL; + } + + timerPosition += raveTicks; + // Wait till syncTime passed, then show specific animation bitmap + if (timerPosition > 0) { + do { + g_sci->getEngineState()->wait(1); + curEvent = _event->getSciEvent(SCI_EVENT_ANY); + if (curEvent.type == SCI_EVENT_MOUSE_PRESS || + (curEvent.type == SCI_EVENT_KEYBOARD && curEvent.data == SCI_KEY_ESC) || + g_sci->getEngineState()->abortScriptProcessing == kAbortQuitGame) + userAbort = true; + curPosition = _audio->getAudioPosition(); + } while ((curPosition != -1) && (curPosition < timerPosition) && (!userAbort)); + } + + if (raveLipSyncData) { + // lip sync data is + // Tick:Byte, Bitmap-Nr:BYTE + // Tick = 0xFF is the terminator for the data + timerPositionWithin = timerPosition; + raveLipSyncTicks = *raveLipSyncData++; + while ( (raveLipSyncData < _lipSyncDataOffsetTableEnd) && (raveLipSyncTicks != 0xFF) ) { + timerPositionWithin += raveLipSyncTicks; + + do { + g_sci->getEngineState()->wait(1); + curEvent = _event->getSciEvent(SCI_EVENT_ANY); + if (curEvent.type == SCI_EVENT_MOUSE_PRESS || + (curEvent.type == SCI_EVENT_KEYBOARD && curEvent.data == SCI_KEY_ESC) || + g_sci->getEngineState()->abortScriptProcessing == kAbortQuitGame) + userAbort = true; + curPosition = _audio->getAudioPosition(); + } while ((curPosition != -1) && (curPosition < timerPositionWithin) && (!userAbort)); + + raveLipSyncBitmapNr = *raveLipSyncData++; + + // bitmap nr within sync data is base 1, we need base 0 + raveLipSyncBitmapNr--; + + if (raveLipSyncBitmapNr < _bitmapCount) { + drawBitmap(0); + drawBitmap(raveLipSyncBitmapNr); + bitsShow(); + } else { + warning("kPortrait: rave lip sync data tried to draw non-existent bitmap %d", raveLipSyncBitmapNr); + } + + raveLipSyncTicks = *raveLipSyncData++; + } + } + } + +// old sync resource code +#if 0 if (!syncResource) { // Getting the book in the book shop calls kPortrait where no sync exists // TODO: find out what to do then @@ -219,6 +358,7 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint } } } +#endif if (userAbort) { // Reset the portrait bitmap to "closed mouth" state, when skipping dialogs @@ -227,7 +367,81 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint _audio->stopAudio(); } + _resMan->unlockResource(raveResource); + +#if 0 _resMan->unlockResource(syncResource); +#endif +} + +// returns ASCII ticks from lip sync string as uint16 +int16 Portrait::raveGetTicks(Resource *resource, uint *offset) { + uint curOffset = *offset; + byte *curData = resource->data + curOffset; + byte curByte; + uint16 curValue = 0; + + if (curOffset >= resource->size) + return -1; + + while (curOffset < resource->size) { + curByte = *curData++; curOffset++; + if ( curByte == ' ' ) + break; + if ( (curByte >= '0') && (curByte <= '9') ) { + curValue = curValue * 10 + ( curByte - '0' ); + } else { + // no number -> assume there is an ID at current offset + return 0; + } + } + *offset = curOffset; + return curValue; +} + +// returns ASCII ID from lip sync string as uint16 +uint16 Portrait::raveGetID(Resource *resource, uint *offset) { + uint curOffset = *offset; + byte *curData = resource->data + curOffset; + byte curByte = 0; + uint16 curValue = 0; + + while (curOffset < resource->size) { + curByte = *curData++; curOffset++; + if ( curByte == ' ' ) + break; + if (!curValue) { + curValue = curByte << 8; + } else { + curValue |= curByte; + } + } + + *offset = curOffset; + return curValue; +} + +// Searches for a specific lip sync ID and returns pointer to lip sync data or NULL in case ID was not found +byte *Portrait::raveGetLipSyncData(uint16 raveID) { + uint lipSyncIDNr = 0; + byte *lipSyncIDPtr = _lipSyncIDTable; + byte lipSyncIDByte1, lipSyncIDByte2; + uint16 lipSyncID; + + lipSyncIDPtr++; // skip over first byte + while (lipSyncIDNr < _lipSyncIDCount) { + lipSyncIDByte1 = *lipSyncIDPtr++; + lipSyncIDByte2 = *lipSyncIDPtr++; + lipSyncID = ( lipSyncIDByte1 << 8 ) | lipSyncIDByte2; + + if ( lipSyncID == raveID ) { + return _lipSyncData + _lipSyncDataOffsetTable[lipSyncIDNr]; + } + + lipSyncIDNr++; + lipSyncIDPtr += 2; // ID is every 4 bytes + } + return NULL; } void Portrait::drawBitmap(uint16 bitmapNr) { diff --git a/engines/sci/graphics/portrait.h b/engines/sci/graphics/portrait.h index 75baa9a56b..de0dbffb3f 100644 --- a/engines/sci/graphics/portrait.h +++ b/engines/sci/graphics/portrait.h @@ -52,6 +52,10 @@ private: void drawBitmap(uint16 bitmapNr); void bitsShow(); + int16 raveGetTicks(Resource *resource, uint *offset); + uint16 raveGetID(Resource *resource, uint *offset); + byte *raveGetLipSyncData(uint16 raveID); + ResourceManager *_resMan; EventManager *_event; GfxPalette *_palette; @@ -68,6 +72,13 @@ private: Common::String _resourceName; byte *_fileData; + + uint32 _lipSyncIDCount; + byte *_lipSyncIDTable; + + byte *_lipSyncData; + uint16 *_lipSyncDataOffsetTable; + byte *_lipSyncDataOffsetTableEnd; Common::Point _position; }; diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp index 8c39edc42e..268180b531 100644 --- a/engines/sci/resource_audio.cpp +++ b/engines/sci/resource_audio.cpp @@ -101,32 +101,34 @@ bool Resource::loadFromAudioVolumeSCI11(Common::SeekableReadStream *file) { } file->seek(-4, SEEK_CUR); - ResourceType type = _resMan->convertResType(file->readByte()); - if (((getType() == kResourceTypeAudio || getType() == kResourceTypeAudio36) && (type != kResourceTypeAudio)) - || ((getType() == kResourceTypeSync || getType() == kResourceTypeSync36) && (type != kResourceTypeSync))) { - warning("Resource type mismatch loading %s", _id.toString().c_str()); - unalloc(); - return false; - } - - _headerSize = file->readByte(); - - if (type == kResourceTypeAudio) { - if (_headerSize != 7 && _headerSize != 11 && _headerSize != 12) { - warning("Unsupported audio header"); + // Rave-resources (King's Quest 6) don't have any header at all + if (getType() != kResourceTypeRave) { + ResourceType type = _resMan->convertResType(file->readByte()); + if (((getType() == kResourceTypeAudio || getType() == kResourceTypeAudio36) && (type != kResourceTypeAudio)) + || ((getType() == kResourceTypeSync || getType() == kResourceTypeSync36) && (type != kResourceTypeSync))) { + warning("Resource type mismatch loading %s", _id.toString().c_str()); unalloc(); return false; } + + _headerSize = file->readByte(); + + if (type == kResourceTypeAudio) { + if (_headerSize != 7 && _headerSize != 11 && _headerSize != 12) { + warning("Unsupported audio header"); + unalloc(); + return false; + } - if (_headerSize != 7) { // Size is defined already from the map - // Load sample size - file->seek(7, SEEK_CUR); - size = file->readUint32LE(); - // Adjust offset to point at the header data again - file->seek(-11, SEEK_CUR); + if (_headerSize != 7) { // Size is defined already from the map + // Load sample size + file->seek(7, SEEK_CUR); + size = file->readUint32LE(); + // Adjust offset to point at the header data again + file->seek(-11, SEEK_CUR); + } } } - return loadPatch(file); } @@ -863,6 +865,7 @@ void AudioVolumeResourceSource::loadResource(ResourceManager *resMan, Resource * switch (res->getType()) { case kResourceTypeSync: case kResourceTypeSync36: + case kResourceTypeRave: // we should already have a (valid) size break; default: -- cgit v1.2.3