mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-09 11:20:56 +00:00
68602a9e0a
implemented smk movieinfo handling main menu shows correct text now music track handling
543 lines
15 KiB
C++
543 lines
15 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 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 "twine/movies.h"
|
|
#include "common/endian.h"
|
|
#include "common/file.h"
|
|
#include "common/language.h"
|
|
#include "common/str.h"
|
|
#include "common/system.h"
|
|
#include "graphics/managed_surface.h"
|
|
#include "image/gif.h"
|
|
#include "twine/audio/music.h"
|
|
#include "twine/audio/sound.h"
|
|
#include "twine/input.h"
|
|
#include "twine/menu/interface.h"
|
|
#include "twine/renderer/screens.h"
|
|
#include "twine/resources/hqr.h"
|
|
#include "twine/resources/resources.h"
|
|
#include "twine/scene/grid.h"
|
|
#include "twine/shared.h"
|
|
#include "twine/twine.h"
|
|
#include "video/smk_decoder.h"
|
|
|
|
namespace TwinE {
|
|
|
|
/** FLA Frame Opcode types */
|
|
enum FlaFrameOpcode {
|
|
kLoadPalette = 1,
|
|
kFade = 2,
|
|
kPlaySample = 3,
|
|
kSampleBalance = 4,
|
|
kStopSample = 5,
|
|
kDeltaFrame = 6,
|
|
kBlackFrame = 7,
|
|
kKeyFrame = 8,
|
|
kCopy = 9,
|
|
kCopy2 = 16
|
|
};
|
|
|
|
/** FLA movie sample structure */
|
|
struct FLASampleStruct {
|
|
/** Number os samples */
|
|
int16 sampleNum = 0;
|
|
/** Sample frequency */
|
|
int16 freq = 0;
|
|
/** Numbers of time to repeat */
|
|
int16 repeat = 0;
|
|
uint8 balance = 0;
|
|
uint8 volumeLeft = 0;
|
|
uint8 volumeRight = 0;
|
|
};
|
|
|
|
/** FLA movie extension */
|
|
#define FLA_EXT ".fla"
|
|
|
|
void Movies::drawKeyFrame(Common::MemoryReadStream &stream, int32 width, int32 height) {
|
|
uint8 *destPtr = (uint8 *)_flaBuffer;
|
|
uint8 *startOfLine = destPtr;
|
|
|
|
for (int32 y = 0; y < height; ++y) {
|
|
const int8 lineEntryCount = stream.readByte();
|
|
|
|
for (int8 a = 0; a < lineEntryCount; a++) {
|
|
const int8 rleFlag = stream.readByte();
|
|
|
|
if (rleFlag < 0) {
|
|
const int8 rleCnt = ABS(rleFlag);
|
|
for (int8 b = 0; b < rleCnt; ++b) {
|
|
*destPtr++ = stream.readByte();
|
|
}
|
|
} else {
|
|
const char colorFill = stream.readByte();
|
|
Common::fill(&destPtr[0], &destPtr[rleFlag], colorFill);
|
|
destPtr += rleFlag;
|
|
}
|
|
}
|
|
|
|
startOfLine = destPtr = startOfLine + width;
|
|
}
|
|
}
|
|
|
|
void Movies::drawDeltaFrame(Common::MemoryReadStream &stream, int32 width) {
|
|
const uint16 skip = stream.readUint16LE() * width;
|
|
const int32 height = stream.readSint16LE();
|
|
|
|
uint8 *destPtr = (uint8 *)_flaBuffer + skip;
|
|
uint8 *startOfLine = destPtr;
|
|
for (int32 y = 0; y < height; ++y) {
|
|
const int8 lineEntryCount = stream.readByte();
|
|
|
|
for (int8 a = 0; a < lineEntryCount; ++a) {
|
|
destPtr += stream.readByte();
|
|
const int8 rleFlag = stream.readByte();
|
|
|
|
if (rleFlag > 0) {
|
|
for (int8 b = 0; b < rleFlag; ++b) {
|
|
*destPtr++ = stream.readByte();
|
|
}
|
|
} else {
|
|
const char colorFill = stream.readByte();
|
|
const int8 rleCnt = ABS(rleFlag);
|
|
Common::fill(&destPtr[0], &destPtr[rleCnt], colorFill);
|
|
destPtr += rleCnt;
|
|
}
|
|
}
|
|
|
|
startOfLine = destPtr = startOfLine + width;
|
|
}
|
|
}
|
|
|
|
void Movies::scaleFla2x() {
|
|
uint8 *source = (uint8 *)_flaBuffer;
|
|
uint8 *dest = (uint8 *)_engine->_imageBuffer.getPixels();
|
|
|
|
if (_engine->_cfgfile.Movie == CONF_MOVIE_FLAWIDE) {
|
|
Common::fill(&dest[0], &dest[_engine->_imageBuffer.w * 40], 0x00);
|
|
dest += _engine->_imageBuffer.w * 40;
|
|
}
|
|
|
|
for (int32 i = 0; i < FLASCREEN_HEIGHT; i++) {
|
|
for (int32 j = 0; j < FLASCREEN_WIDTH; j++) {
|
|
*dest++ = *source;
|
|
*dest++ = *source++;
|
|
}
|
|
if (_engine->_cfgfile.Movie == CONF_MOVIE_FLAWIDE) { // include wide bars
|
|
memcpy(dest, dest - _engine->_imageBuffer.w, FLASCREEN_WIDTH * 2);
|
|
dest += FLASCREEN_WIDTH * 2;
|
|
} else { // stretch the movie like original game.
|
|
if (i % 2) {
|
|
memcpy(dest, dest - _engine->_imageBuffer.w, FLASCREEN_WIDTH * 2);
|
|
dest += FLASCREEN_WIDTH * 2;
|
|
}
|
|
if (i % 10) {
|
|
memcpy(dest, dest - _engine->_imageBuffer.w, FLASCREEN_WIDTH * 2);
|
|
dest += FLASCREEN_WIDTH * 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_engine->_cfgfile.Movie == CONF_MOVIE_FLAWIDE) {
|
|
Common::fill(&dest[0], &dest[_engine->_imageBuffer.w * 40], 0x00);
|
|
}
|
|
}
|
|
|
|
void Movies::processFrame() {
|
|
FLASampleStruct sample;
|
|
|
|
_frameData.videoSize = _file.readSint16LE();
|
|
_frameData.frameVar0 = _file.readSint32LE();
|
|
if (_frameData.frameVar0 > _engine->_imageBuffer.w * _engine->_imageBuffer.h) {
|
|
warning("Skipping video frame - it would exceed the screen buffer: %i", _frameData.frameVar0);
|
|
return;
|
|
}
|
|
|
|
uint8 *outBuf = (uint8 *)_engine->_imageBuffer.getPixels();
|
|
_file.read(outBuf, _frameData.frameVar0);
|
|
|
|
if ((int32)_frameData.videoSize <= 0) {
|
|
return;
|
|
}
|
|
|
|
Common::MemoryReadStream stream(outBuf, _frameData.frameVar0);
|
|
for (int32 frame = 0; frame < _frameData.videoSize; ++frame) {
|
|
const uint16 opcode = stream.readUint16LE();
|
|
const uint16 opcodeBlockSize = stream.readUint16LE();
|
|
const int32 pos = stream.pos();
|
|
|
|
switch (opcode) {
|
|
case kLoadPalette: {
|
|
int16 numOfColor = stream.readSint16LE();
|
|
int16 startColor = stream.readSint16LE();
|
|
uint8 *dest = _engine->_screens->_palette + (startColor * 3);
|
|
stream.read(dest, numOfColor * 3);
|
|
break;
|
|
}
|
|
case kFade: {
|
|
int16 innerOpcpde = stream.readSint16LE();
|
|
switch (innerOpcpde) {
|
|
case 1: // fla flute
|
|
_engine->_music->playMidiMusic(26);
|
|
break;
|
|
case 2:
|
|
// FLA movies don't use cross fade
|
|
// fade out tricky
|
|
if (_fadeOut != 1) {
|
|
_engine->_screens->convertPalToRGBA(_engine->_screens->_palette, _engine->_screens->_paletteRGBACustom);
|
|
_engine->_screens->fadeToBlack(_engine->_screens->_paletteRGBACustom);
|
|
_fadeOut = 1;
|
|
}
|
|
break;
|
|
case 3:
|
|
_flaPaletteVar = true;
|
|
break;
|
|
case 4:
|
|
_engine->_music->stopMidiMusic();
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case kPlaySample: {
|
|
sample.sampleNum = stream.readSint16LE();
|
|
sample.freq = stream.readSint16LE();
|
|
sample.repeat = stream.readSint16LE();
|
|
sample.balance = stream.readByte();
|
|
sample.volumeLeft = stream.readByte();
|
|
sample.volumeRight = stream.readByte();
|
|
_engine->_sound->playFlaSample(sample.sampleNum, sample.repeat, sample.balance, sample.volumeLeft, sample.volumeRight);
|
|
break;
|
|
}
|
|
case kStopSample: {
|
|
const int16 sampleNum = stream.readSint16LE();
|
|
if (sampleNum == -1) {
|
|
_engine->_sound->stopSamples();
|
|
} else {
|
|
_engine->_sound->stopSample(sampleNum);
|
|
}
|
|
break;
|
|
}
|
|
case kDeltaFrame: {
|
|
drawDeltaFrame(stream, FLASCREEN_WIDTH);
|
|
if (_fadeOut == 1) {
|
|
++_fadeOutFrames;
|
|
}
|
|
break;
|
|
}
|
|
case kKeyFrame: {
|
|
drawKeyFrame(stream, FLASCREEN_WIDTH, FLASCREEN_HEIGHT);
|
|
break;
|
|
}
|
|
case kBlackFrame: {
|
|
const Common::Rect rect(0, 0, 79, 199);
|
|
_engine->_interface->drawFilledRect(rect, 0);
|
|
break;
|
|
}
|
|
case kCopy:
|
|
case kCopy2: {
|
|
const Common::Rect rect(0, 0, 80, 200);
|
|
byte *ptr = (byte *)_engine->_frontVideoBuffer.getPixels();
|
|
for (int y = rect.top; y < rect.bottom; ++y) {
|
|
for (int x = rect.left; x < rect.right; ++x) {
|
|
*ptr++ = stream.readByte();
|
|
}
|
|
ptr = ptr + rect.width();
|
|
}
|
|
_engine->_frontVideoBuffer.addDirtyRect(rect);
|
|
break;
|
|
}
|
|
case kSampleBalance: {
|
|
/* int16 num = */ stream.readSint16LE();
|
|
/* uint8 offset = */ stream.readByte();
|
|
/* int16 balance = */ stream.readSint16LE();
|
|
/* uint8 volumeLeft = */ stream.readByte();
|
|
/* uint8 volumeRight = */ stream.readByte();
|
|
// TODO: change balance
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
|
|
stream.seek(pos + opcodeBlockSize);
|
|
}
|
|
}
|
|
|
|
Movies::Movies(TwinEEngine *engine) : _engine(engine) {}
|
|
|
|
void Movies::prepareGIF(int index) {
|
|
Image::GIFDecoder decoder;
|
|
Common::SeekableReadStream *stream = HQR::makeReadStream(Resources::HQR_FLAGIF_FILE, index);
|
|
if (stream == nullptr) {
|
|
warning("Failed to load gif hqr entry with id %i from %s", index, Resources::HQR_FLAGIF_FILE);
|
|
return;
|
|
}
|
|
if (!decoder.loadStream(*stream)) {
|
|
delete stream;
|
|
warning("Failed to load gif with id %i from %s", index, Resources::HQR_FLAGIF_FILE);
|
|
return;
|
|
}
|
|
const Graphics::Surface *surface = decoder.getSurface();
|
|
_engine->setPalette(0, decoder.getPaletteColorCount(), decoder.getPalette());
|
|
Graphics::ManagedSurface& target = _engine->_frontVideoBuffer;
|
|
const Common::Rect surfaceBounds(0, 0, surface->w, surface->h);
|
|
target.transBlitFrom(surface, surfaceBounds, target.getBounds(), 0, false, 0, 0xff, nullptr, true);
|
|
debug(2, "Show gif with id %i from %s", index, Resources::HQR_FLAGIF_FILE);
|
|
delete stream;
|
|
_engine->delaySkip(5000);
|
|
_engine->setPalette(_engine->_screens->_paletteRGBA);
|
|
}
|
|
|
|
void Movies::playGIFMovie(const char *flaName) {
|
|
if (!Common::File::exists(Resources::HQR_FLAGIF_FILE)) {
|
|
warning("%s file doesn't exist", Resources::HQR_FLAGIF_FILE);
|
|
return;
|
|
}
|
|
|
|
Common::String name(flaName);
|
|
name.toLowercase();
|
|
|
|
debug(1, "Play gif %s", name.c_str());
|
|
// TODO: use the HQR 23th entry (movies informations)
|
|
// TODO: there are gifs [1-18]
|
|
if (name == FLA_INTROD) {
|
|
prepareGIF(3);
|
|
prepareGIF(4);
|
|
prepareGIF(5);
|
|
} else if (name == "bateau" || name == "bateau2") {
|
|
prepareGIF(7);
|
|
} else if (name == "navette") {
|
|
prepareGIF(15);
|
|
} else if (name == "templebu") {
|
|
prepareGIF(12);
|
|
} else if (name == "flute2") {
|
|
prepareGIF(8); // TODO: same as glass2?
|
|
} else if (name == "glass2") {
|
|
prepareGIF(8); // TODO: same as flute2?
|
|
} else if (name == "surf") {
|
|
prepareGIF(9);
|
|
} else if (name == "verser" || name == "verser2") {
|
|
prepareGIF(10);
|
|
} else if (name == "neige2") {
|
|
prepareGIF(11);
|
|
} else if (name == "capture") {
|
|
prepareGIF(14); // TODO: same as sendel?
|
|
} else if (name == "sendel") {
|
|
prepareGIF(14); // TODO: same as capture?
|
|
} else if (name == "sendel2") {
|
|
prepareGIF(17);
|
|
} else if (name == FLA_DRAGON3) {
|
|
prepareGIF(1);
|
|
prepareGIF(2);
|
|
} else if (name == "baffe" || name.matchString("baffe#")) {
|
|
prepareGIF(6);
|
|
} else {
|
|
warning("unknown gif image: %s", name.c_str());
|
|
}
|
|
}
|
|
|
|
bool Movies::playMovie(const char *name) {
|
|
if (_engine->isLBA2()) {
|
|
const int index = _engine->_resources->findSmkMovieIndex(name);
|
|
return playSmkMovie(name, index);
|
|
}
|
|
_engine->_sound->stopSamples();
|
|
|
|
Common::String fileNamePath = name;
|
|
const size_t n = fileNamePath.findLastOf(".");
|
|
if (n != Common::String::npos) {
|
|
fileNamePath.erase(n);
|
|
}
|
|
|
|
if (_engine->_cfgfile.Movie == CONF_MOVIE_FLAGIF) {
|
|
playGIFMovie(fileNamePath.c_str());
|
|
return true;
|
|
}
|
|
|
|
_engine->_music->stopMusic();
|
|
|
|
_fadeOut = -1;
|
|
_fadeOutFrames = 0;
|
|
|
|
_file.close();
|
|
if (!_file.open(fileNamePath + FLA_EXT)) {
|
|
warning("Failed to open fla movie '%s'", fileNamePath.c_str());
|
|
playGIFMovie(fileNamePath.c_str());
|
|
return true;
|
|
}
|
|
|
|
const uint32 version = _file.readUint32LE();
|
|
_file.skip(2);
|
|
_flaHeaderData.numOfFrames = _file.readUint32LE();
|
|
_flaHeaderData.speed = _file.readByte();
|
|
_flaHeaderData.var1 = _file.readByte();
|
|
debug(2, "Unknown byte in fla file: %i", _flaHeaderData.var1);
|
|
_flaHeaderData.xsize = _file.readUint16LE();
|
|
_flaHeaderData.ysize = _file.readUint16LE();
|
|
|
|
_samplesInFla = (int16)_file.readUint16LE();
|
|
const uint16 unk2 = _file.readUint16LE();
|
|
debug(2, "Unknown uint16 in fla file: %i", unk2);
|
|
|
|
_file.skip(4 * _samplesInFla);
|
|
|
|
bool finished = false;
|
|
if (version != MKTAG('V', '1', '.', '3')) {
|
|
int32 currentFrame = 0;
|
|
|
|
debug("Play fla: %s", name);
|
|
|
|
ScopedKeyMap scopedKeyMap(_engine, cutsceneKeyMapId);
|
|
|
|
_flaPaletteVar = true;
|
|
do {
|
|
FrameMarker frame(_engine, _flaHeaderData.speed);
|
|
_engine->readKeys();
|
|
if (_engine->shouldQuit()) {
|
|
break;
|
|
}
|
|
if (currentFrame == _flaHeaderData.numOfFrames) {
|
|
finished = true;
|
|
break;
|
|
}
|
|
processFrame();
|
|
scaleFla2x();
|
|
_engine->_frontVideoBuffer.blitFrom(_engine->_imageBuffer, _engine->_imageBuffer.getBounds(), _engine->_frontVideoBuffer.getBounds());
|
|
|
|
// Only blit to screen if isn't a fade
|
|
if (_fadeOut == -1) {
|
|
_engine->_screens->convertPalToRGBA(_engine->_screens->_palette, _engine->_screens->_paletteRGBACustom);
|
|
if (currentFrame == 0) {
|
|
// fade in the first frame
|
|
_engine->_screens->fadeIn(_engine->_screens->_paletteRGBACustom);
|
|
} else {
|
|
_engine->setPalette(_engine->_screens->_paletteRGBACustom);
|
|
}
|
|
}
|
|
|
|
// TRICKY: fade in tricky
|
|
if (_fadeOutFrames >= 2) {
|
|
_engine->_screens->convertPalToRGBA(_engine->_screens->_palette, _engine->_screens->_paletteRGBACustom);
|
|
_engine->_screens->fadeToPal(_engine->_screens->_paletteRGBACustom);
|
|
_fadeOut = -1;
|
|
_fadeOutFrames = 0;
|
|
}
|
|
|
|
currentFrame++;
|
|
} while (!_engine->_input->toggleAbortAction());
|
|
}
|
|
|
|
if (_engine->_cfgfile.CrossFade) {
|
|
_engine->crossFade(_engine->_screens->_paletteRGBACustom);
|
|
} else {
|
|
_engine->_screens->fadeToBlack(_engine->_screens->_paletteRGBACustom);
|
|
}
|
|
|
|
_engine->_sound->stopSamples();
|
|
return finished;
|
|
}
|
|
|
|
class TwineSmackerDecoder : public Video::SmackerDecoder {
|
|
public:
|
|
void enableLanguage(int track, int volume) {
|
|
AudioTrack* audio = getAudioTrack(track);
|
|
if (audio == nullptr) {
|
|
return;
|
|
}
|
|
audio->setMute(false);
|
|
audio->setVolume(CLIP<int>(volume, 0, Audio::Mixer::kMaxMixerVolume));
|
|
}
|
|
};
|
|
|
|
bool Movies::playSmkMovie(const char *name, int index) {
|
|
assert(_engine->isLBA2());
|
|
TwineSmackerDecoder decoder;
|
|
Common::SeekableReadStream *stream = HQR::makeReadStream(TwineResource(Resources::HQR_VIDEO_FILE, index));
|
|
if (stream == nullptr) {
|
|
warning("Failed to find smacker video %i", index);
|
|
return false;
|
|
}
|
|
if (!decoder.loadStream(stream)) {
|
|
warning("Failed to load smacker video %i", index);
|
|
return false;
|
|
}
|
|
const int volume = _engine->_system->getMixer()->getVolumeForSoundType(Audio::Mixer::kMusicSoundType);
|
|
decoder.setVolume(CLIP<int>(volume, 0, Audio::Mixer::kMaxMixerVolume));
|
|
decoder.start();
|
|
|
|
decoder.setAudioTrack(0); // music
|
|
if (_engine->_cfgfile.Voice) {
|
|
int additionalAudioTrack = -1;
|
|
if (!scumm_strnicmp(name, "INTRO", 5)) {
|
|
switch (_engine->getGameLang()) {
|
|
default:
|
|
case Common::Language::EN_ANY:
|
|
case Common::Language::EN_GRB:
|
|
case Common::Language::EN_USA:
|
|
additionalAudioTrack = 3;
|
|
break;
|
|
case Common::Language::DE_DEU:
|
|
additionalAudioTrack = 2;
|
|
break;
|
|
case Common::Language::FR_FRA:
|
|
additionalAudioTrack = 1;
|
|
break;
|
|
}
|
|
}
|
|
const int speechVolume = _engine->_system->getMixer()->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType);
|
|
debug(3, "Play additional speech track: %i (of %i tracks)", additionalAudioTrack, decoder.getAudioTrackCount());
|
|
decoder.enableLanguage(additionalAudioTrack, speechVolume);
|
|
} else {
|
|
debug(3, "Disabled smacker speech");
|
|
}
|
|
|
|
for (;;) {
|
|
if (decoder.endOfVideo()) {
|
|
break;
|
|
}
|
|
FrameMarker frame(_engine);
|
|
_engine->_input->readKeys();
|
|
if (_engine->shouldQuit() || _engine->_input->toggleAbortAction()) {
|
|
break;
|
|
}
|
|
|
|
if (decoder.needsUpdate()) {
|
|
const Graphics::Surface *frameSurf = decoder.decodeNextFrame();
|
|
if (!frameSurf) {
|
|
continue;
|
|
}
|
|
if (decoder.hasDirtyPalette()) {
|
|
_engine->setPalette(0, 256, decoder.getPalette());
|
|
}
|
|
|
|
Graphics::ManagedSurface& target = _engine->_frontVideoBuffer;
|
|
const Common::Rect frameBounds(0, 0, frameSurf->w, frameSurf->h);
|
|
target.transBlitFrom(*frameSurf, frameBounds, target.getBounds(), 0, false, 0, 0xff, nullptr, true);
|
|
}
|
|
}
|
|
|
|
decoder.close();
|
|
return true;
|
|
}
|
|
|
|
} // namespace TwinE
|