mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-04 09:56:30 +00:00
SCI: rave support (KQ6 hires portrait lip sync)
Thanks to wjp and [md5] for helping
This commit is contained in:
parent
298f4a5c06
commit
693d5e6625
@ -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) {
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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 (_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 (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user