Files
2026-02-02 04:50:13 +01:00

896 lines
30 KiB
C++

/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/memstream.h"
#include "graphics/surface.h"
#include "graphics/macgui/macwidget.h"
#include "video/avi_decoder.h"
#include "video/qt_decoder.h"
#include "director/director.h"
#include "director/cast.h"
#include "director/channel.h"
#include "director/frame.h"
#include "director/movie.h"
#include "director/sprite.h"
#include "director/window.h"
#include "director/castmember/bitmap.h"
#include "director/castmember/filmloop.h"
namespace Director {
FilmLoopCastMember::FilmLoopCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
: CastMember(cast, castId, stream) {
_type = kCastFilmLoop;
_looping = true;
_enableSound = true;
_crop = false;
_center = false;
_index = -1;
// We are ignoring some of the bits in the flags
if (cast->_version >= kFileVer400 && cast->_version < kFileVer500) {
_initialRect = Movie::readRect(stream);
uint32 flags = stream.readUint32BE();
uint16 unk1 = stream.readUint16BE();
debugC(5, kDebugLoading, "FilmLoopCastMember::FilmLoopCastMember(): flags: %d, unk1: %d", flags, unk1);
_looping = flags & 64 ? 0 : 1;
_enableSound = flags & 8 ? 1 : 0;
_crop = flags & 2 ? 0 : 1;
_center = flags & 1 ? 1 : 0;
} else if (cast->_version >= kFileVer500 && cast->_version < kFileVer600) {
_initialRect = Movie::readRect(stream);
uint32 flags = stream.readUint32BE();
uint16 unk1 = stream.readUint16BE();
debugC(5, kDebugLoading, "FilmLoopCastMember::FilmLoopCastMember(): flags: %d, unk1: %d", flags, unk1);
_looping = flags & 32 ? 0 : 1;
_enableSound = flags & 8 ? 1 : 0;
_crop = flags & 2 ? 0 : 1;
_center = flags & 1 ? 1 : 0;
}
}
FilmLoopCastMember::FilmLoopCastMember(Cast *cast, uint16 castId, FilmLoopCastMember &source)
: CastMember(cast, castId) {
_type = kCastFilmLoop;
// force a load so we can copy the cast resource information
source.load();
_loaded = true;
_initialRect = source._initialRect;
_boundingRect = source._boundingRect;
if (cast == source._cast)
_children = source._children;
_enableSound = source._enableSound;
_crop = source._crop;
_center = source._center;
_frames = source._frames;
_subchannels = source._subchannels;
_looping = source._looping;
}
FilmLoopCastMember::~FilmLoopCastMember() {
}
bool FilmLoopCastMember::isModified() {
if (_frames.size())
return true;
if (_initialRect.width() && _initialRect.height())
return true;
return false;
}
Common::Array<Channel> *FilmLoopCastMember::getSubChannels(Common::Rect &bbox, uint frame) {
Common::Rect widgetRect(bbox.width() ? bbox.width() : _initialRect.width(), bbox.height() ? bbox.height() : _initialRect.height());
_subchannels.clear();
if (frame >= _frames.size()) {
warning("FilmLoopCastMember::getSubChannels(): Film loop frame %d requested, only %d available", frame, _frames.size());
return &_subchannels;
}
// get the list of sprite IDs for this frame
Common::Array<int> spriteIds;
for (auto &iter : _frames[frame].sprites) {
// channels 0 and 1 are used for audio
if (iter._key >= 2)
spriteIds.push_back(iter._key);
}
Common::sort(spriteIds.begin(), spriteIds.end());
debugC(5, kDebugImages, "FilmLoopCastMember::getSubChannels(): castId: %d, frame: %d, count: %d, initRect: %d,%d %dx%d, bbox: %d,%d %dx%d",
_castId, frame, spriteIds.size(),
_initialRect.left + _initialRect.width()/2,
_initialRect.top + _initialRect.height()/2,
_initialRect.width(), _initialRect.height(),
bbox.left + bbox.width()/2,
bbox.top + bbox.height()/2,
bbox.width(), bbox.height());
// copy the sprites in order to the list
for (auto &iter : spriteIds) {
Sprite src = _frames[frame].sprites[iter];
if (!src._cast)
continue;
// translate sprite relative to the global bounding box
int16 relX = (src._startPoint.x - _initialRect.left) * widgetRect.width() / _initialRect.width();
int16 relY = (src._startPoint.y - _initialRect.top) * widgetRect.height() / _initialRect.height();
int16 absX = relX + bbox.left;
int16 absY = relY + bbox.top;
int16 width = src._width * widgetRect.width() / _initialRect.width();
int16 height = src._height * widgetRect.height() / _initialRect.height();
debugC(5, kDebugImages, "FilmLoopCastMember::getSubChannels(): sprite: %d - cast: %s, orig: %d,%d %dx%d, trans: %d,%d %dx%d",
iter, src._castId.asString().c_str(),
src._startPoint.x, src._startPoint.y, src._width, src._height,
absX, absY, width, height);
// Re-inject the translated position into the Sprite.
// This saves the hassle of having to force the Channel to be in puppet mode.
src._width = width;
src._height = height;
src._startPoint = Common::Point(absX, absY);
src._stretch = true;
// Film loop frames are constructed as a series of Channels, much like how a normal frame
// is rendered by the Score. We don't include a pointer to the current Score here,
// that's only for querying the constraint channel which is not used.
Channel chan(nullptr, &src);
_subchannels.push_back(chan);
}
// Initialise the widgets on all of the subchannels.
// This has to be done once the list has been constructed, otherwise
// the list grow operation will erase the widgets as they aren't
// part of the Channel assignment constructor.
for (auto &iter : _subchannels) {
iter.replaceWidget();
}
return &_subchannels;
}
CastMemberID FilmLoopCastMember::getSubChannelSound1(uint frame) {
if (frame >= _frames.size()) {
warning("FilmLoopCastMember::getSubChannelSound1(): Film loop frame %d requested, only %d available", frame, _frames.size());
return CastMemberID();
}
if (_frames[frame].sprites.contains(0)) {
return _frames[frame].sprites[0]._castId;
}
return CastMemberID();
}
CastMemberID FilmLoopCastMember::getSubChannelSound2(uint frame) {
if (frame >= _frames.size()) {
warning("FilmLoopCastMember::getSubChannelSound2(): Film loop frame %d requested, only %d available", frame, _frames.size());
return CastMemberID();
}
if (_frames[frame].sprites.contains(1)) {
return _frames[frame].sprites[1]._castId;
}
return CastMemberID();
}
void FilmLoopCastMember::loadFilmLoopDataD2(Common::SeekableReadStreamEndian &stream) {
_initialRect = Common::Rect();
_frames.clear();
uint32 size = stream.readUint32BE();
if (debugChannelSet(5, kDebugLoading)) {
debugC(5, kDebugLoading, "loadFilmLoopDataD2: SCVW body:");
uint32 pos = stream.pos();
stream.seek(0);
stream.hexdump(size);
stream.seek(pos);
}
uint16 channelSize = kSprChannelSizeD2;
FilmLoopFrame newFrame;
while (stream.pos() < size) {
uint16 frameSize = stream.readUint16BE();
if (frameSize == 0) {
continue;
}
frameSize -= 2;
if (debugChannelSet(5, kDebugLoading)) {
debugC(5, kDebugLoading, "loadFilmLoopDataD2: Frame entry:");
stream.hexdump(frameSize);
}
while (frameSize > 0) {
int msgWidth = stream.readByte() * 2;
int order = stream.readByte() * 2 - 0x20;
frameSize -= 2;
int channel = order / channelSize;
int channelOffset = order % channelSize;
int offset = order;
debugC(8, kDebugLoading, "loadFilmLoopDataD2: Message: msgWidth %d, channel %d, channelOffset %d", msgWidth, channel, channelOffset);
if (debugChannelSet(8, kDebugLoading)) {
stream.hexdump(msgWidth);
}
uint16 segSize = msgWidth;
uint16 nextStart = (channel + 1) * kSprChannelSizeD2;
while (segSize > 0) {
Sprite sprite(nullptr);
sprite._movie = g_director->getCurrentMovie();
if (newFrame.sprites.contains(channel)) {
sprite = newFrame.sprites.getVal(channel);
}
sprite._spriteType = kCastMemberSprite;
sprite._stretch = true;
uint16 needSize = MIN((uint16)(nextStart - offset), segSize);
int startPosition = stream.pos() - channelOffset;
int finishPosition = stream.pos() + needSize;
readSpriteDataD2(stream, sprite, startPosition, finishPosition);
newFrame.sprites.setVal(channel, sprite);
segSize -= needSize;
offset += needSize;
channel += 1;
channelOffset = 0;
nextStart += kSprChannelSizeD2;
}
frameSize -= msgWidth;
}
for (auto &s : newFrame.sprites) {
debugC(5, kDebugLoading, "loadFilmLoopDataD2: Sprite: channel %d, castId %s, bbox %d %d %d %d", s._key,
s._value._castId.asString().c_str(), s._value._startPoint.x, s._value._startPoint.y,
s._value._width, s._value._height);
s._value.setCast(s._value._castId);
Common::Point topLeft = s._value._startPoint;
if (s._value._cast) {
topLeft -= s._value._cast->getRegistrationOffset(s._value._width, s._value._height);
}
Common::Rect spriteBbox(
topLeft.x,
topLeft.y,
topLeft.x + s._value._width,
topLeft.y + s._value._height
);
if (!((spriteBbox.width() == 0) && (spriteBbox.height() == 0))) {
if ((_initialRect.width() == 0) && (_initialRect.height() == 0)) {
_initialRect = spriteBbox;
} else {
_initialRect.extend(spriteBbox);
}
}
debugC(8, kDebugLoading, "loadFilmLoopDataD2: New bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
}
_frames.push_back(newFrame);
}
debugC(5, kDebugLoading, "loadFilmLoopDataD2: Full bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
}
void FilmLoopCastMember::loadFilmLoopDataD4(Common::SeekableReadStreamEndian &stream) {
_initialRect = Common::Rect();
_frames.clear();
uint32 size = stream.readUint32BE();
if (debugChannelSet(8, kDebugLoading)) {
debugC(8, kDebugLoading, "loadFilmLoopDataD4: SCVW body of size: %d", size);
uint32 pos = stream.pos();
stream.seek(0);
stream.hexdump(size);
stream.seek(pos);
}
uint32 framesOffset = stream.readUint32BE();
if (debugChannelSet(8, kDebugLoading)) {
debugC(8, kDebugLoading, "loadFilmLoopDataD4: SCVW header of size: %d", framesOffset - 8);
stream.hexdump(framesOffset - 8);
}
stream.skip(6);
uint16 channelSize = kSprChannelSizeD4;
stream.readUint16BE(); // should be kSprChannelSizeD4 = 20!
stream.skip(framesOffset - 16);
FilmLoopFrame newFrame;
while (stream.pos() < size) {
uint16 frameSize = stream.readUint16BE();
if (frameSize == 0) {
continue;
}
frameSize -= 2;
if (debugChannelSet(8, kDebugLoading)) {
debugC(8, kDebugLoading, "loadFilmLoopDataD4: Frame entry: %d", frameSize);
stream.hexdump(frameSize);
}
while (frameSize > 0) {
uint16 msgWidth = stream.readUint16BE();
uint16 order = stream.readUint16BE();
frameSize -= 4;
int channel = order / channelSize;
int channelOffset = order % channelSize;
int offset = order;
debugC(8, kDebugLoading, "loadFilmLoopDataD4: Message: msgWidth %d, order: %d, channel %d, channelOffset %d", msgWidth, order, channel, channelOffset);
if (debugChannelSet(8, kDebugLoading)) {
stream.hexdump(msgWidth);
}
uint16 segSize = msgWidth;
uint16 nextStart = (channel + 1) * kSprChannelSizeD4;
while (segSize > 0) {
Sprite sprite(nullptr);
sprite._movie = g_director->getCurrentMovie();
if (newFrame.sprites.contains(channel)) {
sprite = newFrame.sprites.getVal(channel);
}
sprite._stretch = true;
uint16 needSize = MIN((uint16)(nextStart - offset), segSize);
int startPosition = stream.pos() - channelOffset;
int finishPosition = stream.pos() + needSize;
readSpriteDataD4(stream, sprite, startPosition, finishPosition);
newFrame.sprites.setVal(channel, sprite);
segSize -= needSize;
offset += needSize;
channel += 1;
channelOffset = 0;
nextStart += kSprChannelSizeD4;
}
frameSize -= msgWidth;
}
for (auto &s : newFrame.sprites) {
debugC(8, kDebugLoading, "loadFilmLoopDataD4: Sprite: channel %d, castId %s, bbox %d %d %d %d", s._key,
s._value._castId.asString().c_str(), s._value._startPoint.x, s._value._startPoint.y,
s._value._width, s._value._height);
if (s._key == -1) {
debugC(8, kDebugLoading, "loadFilmLoopDataD4: Skipping channel -1");
if (s._value._startPoint.x != 0 || s._value._startPoint.y != 0 || s._value._width != 0 ||
(s._value._height != -256 && s._value._height != 0))
warning("BUILDBOT: loadFilmLoopDataD4: Malformed VWSC resource: Sprite: channel %d, castId %s, bbox %d %d %d %d", s._key,
s._value._castId.asString().c_str(), s._value._startPoint.x, s._value._startPoint.y,
s._value._width, s._value._height);
continue;
}
s._value.setCast(s._value._castId);
Common::Point topLeft = s._value._startPoint;
if (s._value._cast) {
topLeft -= s._value._cast->getRegistrationOffset(s._value._width, s._value._height);
}
Common::Rect spriteBbox(
topLeft.x,
topLeft.y,
topLeft.x + s._value._width,
topLeft.y + s._value._height
);
if (!((spriteBbox.width() == 0) && (spriteBbox.height() == 0))) {
if ((_initialRect.width() == 0) && (_initialRect.height() == 0)) {
_initialRect = spriteBbox;
} else {
_initialRect.extend(spriteBbox);
}
}
debugC(8, kDebugLoading, "loadFilmLoopDataD4: New bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
}
_frames.push_back(newFrame);
}
debugC(5, kDebugLoading, "loadFilmLoopDataD4: Full bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
}
void FilmLoopCastMember::loadFilmLoopDataD5(Common::SeekableReadStreamEndian &stream) {
_initialRect = Common::Rect();
_frames.clear();
uint32 size = stream.readUint32BE();
if (debugChannelSet(5, kDebugLoading)) {
debugC(5, kDebugLoading, "loadFilmLoopDataD5: SCVW body:");
uint32 pos = stream.pos();
stream.seek(0);
stream.hexdump(size);
stream.seek(pos);
}
uint32 framesOffset = stream.readUint32BE();
if (debugChannelSet(5, kDebugLoading)) {
debugC(5, kDebugLoading, "loadFilmLoopDataD5: SCVW header:");
stream.hexdump(framesOffset - 8);
}
stream.skip(6);
uint16 channelSize = kSprChannelSizeD5;
stream.readUint16BE(); // should be kSprChannelSizeD5 = 24!
stream.skip(framesOffset - 16);
FilmLoopFrame newFrame;
while (stream.pos() < size) {
uint16 frameSize = stream.readUint16BE();
if (frameSize == 0) {
continue;
}
frameSize -= 2;
if (debugChannelSet(5, kDebugLoading)) {
debugC(5, kDebugLoading, "loadFilmLoopDataD5: Frame entry:");
stream.hexdump(frameSize);
}
while (frameSize > 0) {
uint16 msgWidth = stream.readUint16BE();
uint16 order = stream.readUint16BE();
frameSize -= 4;
int channel = order / channelSize;
int channelOffset = order % channelSize;
int offset = order;
debugC(8, kDebugLoading, "loadFilmLoopDataD5: Message: msgWidth %d, channel %d, channelOffset %d", msgWidth, channel, channelOffset);
if (debugChannelSet(8, kDebugLoading)) {
stream.hexdump(msgWidth);
}
uint16 segSize = msgWidth;
uint16 nextStart = (channel + 1) * kSprChannelSizeD5;
while (segSize > 0) {
Sprite sprite(nullptr);
sprite._movie = g_director->getCurrentMovie();
if (newFrame.sprites.contains(channel)) {
sprite = newFrame.sprites.getVal(channel);
}
sprite._stretch = true;
uint16 needSize = MIN((uint16)(nextStart - offset), segSize);
int startPosition = stream.pos() - channelOffset;
int finishPosition = stream.pos() + needSize;
readSpriteDataD5(stream, sprite, startPosition, finishPosition);
// Swap castLib ID value of -1 for the film loop's castLib ID
if (sprite._castId.castLib == -1)
sprite._castId.castLib = _cast->_castLibID;
if (sprite._scriptId.castLib == -1)
sprite._scriptId.castLib = _cast->_castLibID;
newFrame.sprites.setVal(channel, sprite);
segSize -= needSize;
offset += needSize;
channel += 1;
channelOffset = 0;
nextStart += kSprChannelSizeD5;
}
frameSize -= msgWidth;
}
for (auto &s : newFrame.sprites) {
debugC(5, kDebugLoading, "loadFilmLoopDataD5: Sprite: channel %d, castId %s, bbox %d %d %d %d", s._key,
s._value._castId.asString().c_str(), s._value._startPoint.x, s._value._startPoint.y,
s._value._width, s._value._height);
if (s._key == -1) {
debugC(5, kDebugLoading, "loadFilmLoopDataD5: Skipping channel -1");
if (s._value._startPoint.x != 0 || s._value._startPoint.y != 0 || s._value._width != 0 ||
(s._value._height != -256 && s._value._height != 0))
warning("BUILDBOT: loadFilmLoopDataD5: Malformed VWSC resource: Sprite: channel %d, castId %s, bbox %d %d %d %d", s._key,
s._value._castId.asString().c_str(), s._value._startPoint.x, s._value._startPoint.y,
s._value._width, s._value._height);
continue;
}
s._value.setCast(s._value._castId);
Common::Point topLeft = s._value._startPoint;
if (s._value._cast) {
topLeft -= s._value._cast->getRegistrationOffset(s._value._width, s._value._height);
}
Common::Rect spriteBbox(
topLeft.x,
topLeft.y,
topLeft.x + s._value._width,
topLeft.y + s._value._height
);
if (!((spriteBbox.width() == 0) && (spriteBbox.height() == 0))) {
if ((_initialRect.width() == 0) && (_initialRect.height() == 0)) {
_initialRect = spriteBbox;
} else {
_initialRect.extend(spriteBbox);
}
}
debugC(8, kDebugLoading, "loadFilmLoopDataD5: New bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
}
_frames.push_back(newFrame);
}
debugC(5, kDebugLoading, "loadFilmLoopDataD5: Full bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
}
void FilmLoopCastMember::loadFilmLoopDataD6(Common::SeekableReadStreamEndian &stream) {
_initialRect = Common::Rect();
_frames.clear();
uint32 size = stream.readUint32BE();
if (debugChannelSet(5, kDebugLoading)) {
debugC(5, kDebugLoading, "loadFilmLoopDataD6: SCVW body:");
uint32 pos = stream.pos();
stream.seek(0);
stream.hexdump(size);
stream.seek(pos);
}
uint32 framesOffset = stream.readUint32BE();
if (debugChannelSet(5, kDebugLoading)) {
debugC(5, kDebugLoading, "loadFilmLoopDataD6: SCVW header:");
stream.hexdump(framesOffset - 8);
}
stream.skip(6);
uint16 channelSize = kSprChannelSizeD6;
stream.readUint16BE(); // should be kSprChannelSizeD6 = 24!
stream.skip(framesOffset - 16);
FilmLoopFrame newFrame;
while (stream.pos() < size) {
uint16 frameSize = stream.readUint16BE();
if (frameSize == 0) {
continue;
}
frameSize -= 2;
if (debugChannelSet(5, kDebugLoading)) {
debugC(5, kDebugLoading, "loadFilmLoopDataD6: Frame entry:");
stream.hexdump(frameSize);
}
while (frameSize > 0) {
uint16 msgWidth = stream.readUint16BE();
uint16 order = stream.readUint16BE();
frameSize -= 4;
int channel = order / channelSize;
int channelOffset = order % channelSize;
int offset = order;
debugC(8, kDebugLoading, "loadFilmLoopDataD6: Message: msgWidth %d, channel %d, channelOffset %d", msgWidth, channel, channelOffset);
if (debugChannelSet(8, kDebugLoading)) {
stream.hexdump(msgWidth);
}
uint16 segSize = msgWidth;
uint16 nextStart = (channel + 1) * kSprChannelSizeD4;
while (segSize > 0) {
Sprite sprite(nullptr);
sprite._movie = g_director->getCurrentMovie();
if (newFrame.sprites.contains(channel)) {
sprite = newFrame.sprites.getVal(channel);
}
sprite._stretch = true;
uint16 needSize = MIN((uint16)(nextStart - offset), segSize);
int startPosition = stream.pos() - channelOffset;
int finishPosition = stream.pos() + needSize;
readSpriteDataD6(stream, sprite, startPosition, finishPosition);
// Swap castLib ID value of -1 for the film loop's castLib ID
if (sprite._castId.castLib == -1)
sprite._castId.castLib = _cast->_castLibID;
if (sprite._scriptId.castLib == -1)
sprite._scriptId.castLib = _cast->_castLibID;
newFrame.sprites.setVal(channel, sprite);
segSize -= needSize;
offset += needSize;
channel += 1;
channelOffset = 0;
nextStart += kSprChannelSizeD6;
}
frameSize -= msgWidth;
}
for (auto &s : newFrame.sprites) {
debugC(5, kDebugLoading, "loadFilmLoopDataD6: Sprite: channel %d, castId %s, bbox %d %d %d %d", s._key,
s._value._castId.asString().c_str(), s._value._startPoint.x, s._value._startPoint.y,
s._value._width, s._value._height);
if (s._key == -1) {
debugC(5, kDebugLoading, "loadFilmLoopDataD6: Skipping channel -1");
if (s._value._startPoint.x != 0 || s._value._startPoint.y != 0 || s._value._width != 0 ||
(s._value._height != -256 && s._value._height != 0))
warning("BUILDBOT: loadFilmLoopDataD6: Malformed VWSC resource: Sprite: channel %d, castId %s, bbox %d %d %d %d", s._key,
s._value._castId.asString().c_str(), s._value._startPoint.x, s._value._startPoint.y,
s._value._width, s._value._height);
continue;
}
s._value.setCast(s._value._castId);
Common::Point topLeft = s._value._startPoint;
if (s._value._cast) {
topLeft -= s._value._cast->getRegistrationOffset(s._value._width, s._value._height);
}
Common::Rect spriteBbox(
topLeft.x,
topLeft.y,
topLeft.x + s._value._width,
topLeft.y + s._value._height
);
if (!((spriteBbox.width() == 0) && (spriteBbox.height() == 0))) {
if ((_initialRect.width() == 0) && (_initialRect.height() == 0)) {
_initialRect = spriteBbox;
} else {
_initialRect.extend(spriteBbox);
}
}
debugC(8, kDebugLoading, "loadFilmLoopDataD6: New bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
}
_frames.push_back(newFrame);
}
debugC(5, kDebugLoading, "loadFilmLoopDataD6: Full bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
}
Common::String FilmLoopCastMember::formatInfo() {
return Common::String::format(
"initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, frameCount: %d, subchannelCount: %d, enableSound: %d, looping: %d, crop: %d, center: %d",
_initialRect.width(), _initialRect.height(),
_initialRect.left, _initialRect.top,
_boundingRect.width(), _boundingRect.height(),
_boundingRect.left, _boundingRect.top,
_frames.size(), _subchannels.size(), _enableSound, _looping,
_crop, _center
);
}
void FilmLoopCastMember::load() {
if (_loaded)
return;
if (_cast->_version < kFileVer400) {
// Director 3 and below should have a SCVW resource
uint16 filmLoopId = _castId + _cast->_castIDoffset;
uint32 tag = MKTAG('S', 'C', 'V', 'W');
Common::SeekableReadStreamEndian *loop = _cast->getResource(tag, filmLoopId);
if (loop) {
debugC(2, kDebugLoading, "****** Loading '%s' id: %d, %d bytes", tag2str(tag), filmLoopId, (int)loop->size());
loadFilmLoopDataD2(*loop);
delete loop;
} else {
warning("FilmLoopCastMember::load(): Film loop not found");
}
} else if (_cast->_version >= kFileVer400 && _cast->_version < kFileVer700) {
Common::SeekableReadStreamEndian *loop = nullptr;
uint16 filmLoopId = 0;
uint32 tag = 0;
for (auto &it : _children) {
if (it.tag == MKTAG('S', 'C', 'V', 'W')) {
filmLoopId = it.index;
tag = it.tag;
loop = _cast->getResource(tag, filmLoopId);
break;
}
}
if (loop) {
debugC(2, kDebugLoading, "****** Loading '%s' id: %d, %d bytes", tag2str(tag), filmLoopId, (int)loop->size());
if (_cast->_version < kFileVer500) {
loadFilmLoopDataD4(*loop);
} else if (_cast->_version < kFileVer600) {
loadFilmLoopDataD5(*loop);
} else if (_cast->_version < kFileVer700) {
loadFilmLoopDataD6(*loop);
}
delete loop;
} else {
warning("FilmLoopCastMember::load(): No SCVW resource found in %d children", _children.size());
}
} else {
warning("STUB: FilmLoopCastMember::load(): Film loops not yet supported for version v%d (%d)", humanVersion(_cast->_version), _cast->_version);
}
_loaded = true;
}
void FilmLoopCastMember::unload() {
// No unload necessary.
}
Common::Point FilmLoopCastMember::getRegistrationOffset() {
return Common::Point(_initialRect.width() / 2, _initialRect.height() / 2);
}
Common::Point FilmLoopCastMember::getRegistrationOffset(int16 currentWidth, int16 currentHeight) {
return Common::Point(currentWidth / 2, currentHeight / 2);
}
uint32 FilmLoopCastMember::getCastDataSize() {
// We're only reading the _initialRect and _vflags from the Cast Data
// _initialRect : 8 bytes + flags : 4 bytes + 2 bytes unk1 + 2 bytes (castType and _flags1 (see Cast::loadCastData() for Director 4 only)
if (_cast->_version >= kFileVer400 && _cast->_version < kFileVer500) {
// It has been observed that the FilmCastMember has _flags as 0x00
return 8 + 4 + 2 + 2;
} else if (_cast->_version >= kFileVer500 && _cast->_version < kFileVer600) {
return 8 + 4 + 2;
}
warning("FilmLoopCastMember::getCastDataSize(): unhandled or invalid cast version: %d", _cast->_version);
return 0;
}
void FilmLoopCastMember::writeCastData(Common::SeekableWriteStream *writeStream) {
Movie::writeRect(writeStream, _initialRect);
uint32 flags = 0;
if (_cast->_version >= kFileVer400 && _cast->_version < kFileVer500) {
flags |= (_looping) ? 0 : 64;
flags |= (_enableSound) ? 8 : 0;
flags |= (_crop) ? 0 : 2;
flags |= (_center) ? 1 : 0;
} else if (_cast->_version >= kFileVer500 && _cast->_version < kFileVer600) {
flags |= (_looping) ? 0 : 32;
flags |= (_enableSound) ? 8 : 0;
flags |= (_crop) ? 0 : 2;
flags |= (_center) ? 1 : 0;
}
writeStream->writeUint32LE(flags);
writeStream->writeUint16LE(0); // May need to save proper value in the future, currently ignored
}
void FilmLoopCastMember::writeSCVWResource(Common::SeekableWriteStream *writeStream, uint32 offset) {
// Load it before writing
if (!_loaded) {
load();
}
uint32 channelSize = 0;
if (_cast->_version >= kFileVer400 && _cast->_version < kFileVer500) {
channelSize = kSprChannelSizeD4;
} else if (_cast->_version >= kFileVer500 && _cast->_version < kFileVer600) {
channelSize = kSprChannelSizeD5;
} else {
warning("FilmLoopCastMember::writeSCVWResource: Writing Director Version 6+ not supported yet");
return;
}
// Go to the desired offset put in the memory map
writeStream->seek(offset);
uint32 filmloopSize = getSCVWResourceSize();
debugC(5, kDebugSaving, "FilmLoopCastmember::writeSCVWResource: Saving FilmLoop 'SCVW' data of size: %d", filmloopSize);
writeStream->writeUint32LE(MKTAG('S', 'C', 'V', 'W'));
writeStream->writeUint32LE(filmloopSize); // Size of the resource
writeStream->writeUint32BE(filmloopSize);
uint32 frameOffset = 20; // Should be greater than 20
writeStream->writeUint32BE(frameOffset); // framesOffset
writeStream->seek(6, SEEK_CUR); // Ignored data
writeStream->writeUint16BE(channelSize);
writeStream->seek(frameOffset - 16, SEEK_CUR); // Ignored data
// The structure of the filmloop 'SCVW' data is as follows
// The 'SCVW' tag -> the size of the resource ->
// frameoffset (This offset is where the frame date actually starts) ->
// Some headers which we ignore except the Sprite Channel Size (which we also ignore during loading) ->
// until there are no more frames
// size of the frame ->
// until there are no more channels in the frame
// width of message (One chunk of data) (This is the size of data for the sprite that needs to be read) ->
// order of message (this order tells us the channel we're reading) ->
// 1-20 bytes of Sprite data
for (FilmLoopFrame frame : _frames) {
writeStream->writeUint16BE(frame.sprites.size() * (channelSize + 4) + 2); // Frame Size
for (auto it : frame.sprites) {
int channel = it._key;
// TODO: For now writing the order considering that each sprite will have 20 bytes of data
// In the future, for optimization, we can actually calculate the data of each sprite
// And write the order accordingly
// But for this we'll need a way to find how many data values (out of 20) of a sprite are valid, i.e. determine message width
// this means while loading, the channelOffset will always be 0, order will always be multiple of 20
// And message width will always be 20
// Channel indexes start with 0
writeStream->writeUint16BE(channelSize); // message width
writeStream->writeUint16BE(channel * channelSize);
Sprite sprite = it._value;
if (_cast->_version >= kFileVer400 && _cast->_version < kFileVer500) {
writeSpriteDataD4(writeStream, sprite);
} else if (_cast->_version >= kFileVer500 && _cast->_version < kFileVer600) {
writeSpriteDataD5(writeStream, sprite);
}
}
}
if (debugChannelSet(7, kDebugSaving)) {
// Adding +8 because the stream doesn't include the header and the entry for the size itself
byte *dumpData = (byte *)calloc(filmloopSize + 8, sizeof(byte));
Common::SeekableMemoryWriteStream *dumpStream = new Common::SeekableMemoryWriteStream(dumpData, filmloopSize + 8);
uint32 currentPos = writeStream->pos();
writeStream->seek(offset);
dumpStream->write(writeStream, filmloopSize + 8);
writeStream->seek(currentPos);
dumpFile("FilmLoopData", 0, MKTAG('V', 'W', 'C', 'F'), dumpData, filmloopSize);
free(dumpData);
delete dumpStream;
}
}
uint32 FilmLoopCastMember::getSCVWResourceSize() {
uint32 channelSize = 0;
if (_cast->_version >= kFileVer400 && _cast->_version < kFileVer500) {
channelSize = kSprChannelSizeD4;
} else if (_cast->_version >= kFileVer500) {
channelSize = kSprChannelSizeD5;
} else {
warning("FilmLoopCastMember::getSCVWResourceSize: Director version unsupported");
}
uint32 framesSize = 0;
for (FilmLoopFrame frame : _frames) {
// Frame size
framesSize += 2;
for (auto it : frame.sprites) {
// message width: 2 bytes
// order: 2 bytes
// Sprite data: 20 bytes
framesSize += 2 + 2 + channelSize;
}
}
// Size: 4 bytes
// frameoffset: 4 bytes
// Header (Ignored data): 16 bytes
return 4 + 4 + 16 + framesSize;
}
} // End of namespace Director