/* 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/events.h" #include "common/system.h" #include "engines/engine.h" #include "graphics/palette.h" #include "video/flic_decoder.h" #include "chewy/sound.h" #include "chewy/video/cfo_decoder.h" namespace Chewy { enum CustomSubChunk { kChunkFadeIn = 0, // unused kChunkFadeOut = 1, kChunkLoadMusic = 2, kChunkLoadRaw = 3, // unused kChunkLoadVoc = 4, kChunkPlayMusic = 5, kChunkPlaySeq = 6, // unused kChunkPlayPattern = 7, // unused kChunkStopMusic = 8, kChunkWaitMusicEnd = 9, kChunkSetMusicVolume = 10, kChunkSetLoopMode = 11, // unused kChunkPlayRaw = 12, // unused kChunkPlayVoc = 13, kChunkSetSoundVolume = 14, kChunkSetChannelVolume = 15, kChunkFreeSoundEffect = 16, kChunkMusicFadeIn = 17, // unused kChunkMusicFadeOut = 18, kChunkSetBalance = 19, kChunkSetSpeed = 20, // unused kChunkClearScreen = 21 }; bool CfoDecoder::loadStream(Common::SeekableReadStream *stream) { close(); if (stream->readUint32BE() != MKTAG('C', 'F', 'O', '\0')) error("Corrupt video resource"); stream->readUint32LE(); // always 0 uint16 frameCount = stream->readUint16LE(); uint16 width = stream->readUint16LE(); uint16 height = stream->readUint16LE(); addTrack(new CfoVideoTrack(stream, frameCount, width, height, _sound)); return true; } CfoDecoder::CfoVideoTrack::CfoVideoTrack(Common::SeekableReadStream *stream, uint16 frameCount, uint16 width, uint16 height, Sound *sound) : Video::FlicDecoder::FlicVideoTrack(stream, frameCount, width, height, true), _sound(sound) { readHeader(); for (int i = 0; i < MAX_SOUND_EFFECTS; i++) { _soundEffects[i] = nullptr; _soundEffectSize[i] = 0; } _musicData = nullptr; _musicSize = 0; } CfoDecoder::CfoVideoTrack::~CfoVideoTrack() { _sound->stopAll(); for (int i = 0; i < MAX_SOUND_EFFECTS; i++) { delete[] _soundEffects[i]; } delete[] _musicData; } void CfoDecoder::CfoVideoTrack::readHeader() { _frameDelay = _startFrameDelay = _fileStream->readUint32LE(); _offsetFrame1 = _fileStream->readUint32LE(); _offsetFrame2 = 0; // doesn't exist, as CFO videos aren't rewindable _fileStream->seek(_offsetFrame1); } #define FRAME_TYPE 0xF1FA #define CUSTOM_FRAME_TYPE 0xFAF1 const ::Graphics::Surface *CfoDecoder::CfoVideoTrack::decodeNextFrame() { uint16 frameType; // Read chunk /*uint32 frameSize =*/ _fileStream->readUint32LE(); frameType = _fileStream->readUint16LE(); switch (frameType) { case FRAME_TYPE: handleFrame(); break; case CUSTOM_FRAME_TYPE: handleCustomFrame(); break; default: error("CfoDecoder::decodeFrame(): unknown main chunk type (type = 0x%02X)", frameType); break; } _curFrame++; _nextFrameStartTime += _frameDelay; return _surface; } #define FLI_SETPAL 4 #define FLI_SS2 7 #define FLI_BRUN 15 #define FLI_COPY 16 #define PSTAMP 18 void CfoDecoder::CfoVideoTrack::handleFrame() { uint16 chunkCount = _fileStream->readUint16LE(); // Read subchunks for (uint32 i = 0; i < chunkCount; ++i) { uint32 frameSize = _fileStream->readUint32LE(); uint16 frameType = _fileStream->readUint16LE(); uint8 *data = new uint8[frameSize - 6]; _fileStream->read(data, frameSize - 6); switch (frameType) { case FLI_SETPAL: unpackPalette(data); _dirtyPalette = true; break; case FLI_SS2: decodeDeltaFLC(data); break; case FLI_BRUN: decodeByteRun(data); break; case FLI_COPY: copyFrame(data); break; case PSTAMP: /* PSTAMP - skip for now */ break; default: error("CfoDecoder::decodeNextFrame(): unknown subchunk type (type = 0x%02X)", frameType); break; } delete[] data; } } void CfoDecoder::CfoVideoTrack::handleCustomFrame() { uint16 chunkCount = _fileStream->readUint16LE(); uint16 number, channel, volume, repeat, balance; // Read subchunks for (uint32 i = 0; i < chunkCount; ++i) { uint32 frameSize = _fileStream->readUint32LE(); uint16 frameType = _fileStream->readUint16LE(); switch (frameType) { case kChunkFadeIn: error("Unused chunk kChunkFadeIn found"); break; case kChunkFadeOut: // Used in video 0 _fileStream->skip(2); // delay, unused fadeOut(); break; case kChunkLoadMusic: // Used in videos 0, 18, 34, 71 _musicSize = frameSize; _musicData = new byte[frameSize]; _fileStream->read(_musicData, frameSize); break; case kChunkLoadRaw: error("Unused chunk kChunkLoadRaw found"); break; case kChunkLoadVoc: number = _fileStream->readUint16LE(); assert(number < MAX_SOUND_EFFECTS); delete[] _soundEffects[number]; _soundEffectSize[number] = frameSize - 2; _soundEffects[number] = new byte[frameSize - 2]; _fileStream->read(_soundEffects[number], frameSize - 2); break; case kChunkPlayMusic: // Used in videos 0, 18, 34, 71 _sound->playMusic(_musicData, _musicSize, false, DisposeAfterUse::NO); break; case kChunkPlaySeq: error("Unused chunk kChunkPlaySeq found"); break; case kChunkPlayPattern: error("Unused chunk kChunkPlayPattern found"); break; case kChunkStopMusic: _sound->stopMusic(); // Game videos do not restart music after stopping it delete[] _musicData; _musicSize = 0; break; case kChunkWaitMusicEnd: do { Common::Event event; while (g_system->getEventManager()->pollEvent(event)) {} // ignore events g_system->updateScreen(); g_system->delayMillis(10); } while (_sound->isMusicActive()); break; case kChunkSetMusicVolume: volume = _fileStream->readUint16LE() * Audio::Mixer::kMaxChannelVolume / 63; _sound->setMusicVolume(volume); break; case kChunkSetLoopMode: error("Unused chunk kChunkSetLoopMode found"); break; case kChunkPlayRaw: error("Unused chunk kChunkPlayRaw found"); break; case kChunkPlayVoc: number = _fileStream->readUint16LE(); channel = _fileStream->readUint16LE(); volume = _fileStream->readUint16LE() * Audio::Mixer::kMaxChannelVolume / 63; repeat = _fileStream->readUint16LE(); assert(number < MAX_SOUND_EFFECTS); _sound->setSoundVolume(volume); _sound->playSound(_soundEffects[number], _soundEffectSize[number], repeat, channel, DisposeAfterUse::NO); break; case kChunkSetSoundVolume: volume = _fileStream->readUint16LE() * Audio::Mixer::kMaxChannelVolume / 63; _sound->setSoundVolume(volume); break; case kChunkSetChannelVolume: channel = _fileStream->readUint16LE(); volume = _fileStream->readUint16LE() * Audio::Mixer::kMaxChannelVolume / 63; _sound->setSoundChannelVolume(channel, volume); break; case kChunkFreeSoundEffect: number = _fileStream->readUint16LE(); assert(number < MAX_SOUND_EFFECTS); delete[] _soundEffects[number]; _soundEffects[number] = nullptr; break; case kChunkMusicFadeIn: error("Unused chunk kChunkMusicFadeIn found"); break; case kChunkMusicFadeOut: // Used in videos 0, 71 warning("kChunkMusicFadeOut"); // TODO _fileStream->skip(frameSize); break; case kChunkSetBalance: channel = _fileStream->readUint16LE(); balance = (_fileStream->readUint16LE() * 2) - 127; _sound->setSoundChannelBalance(channel, balance); break; case kChunkSetSpeed: error("Unused chunk kChunkSetSpeed found"); break; case kChunkClearScreen: g_system->fillScreen(0); break; default: error("Unknown subchunk: %d", frameType); break; } } } void CfoDecoder::CfoVideoTrack::fadeOut() { for (int j = 0; j < 64; j++) { for (int i = 0; i < 256; i++) { if (_palette[i * 3 + 0] > 0) --_palette[i * 3 + 0]; if (_palette[i * 3 + 1] > 0) --_palette[i * 3 + 1]; if (_palette[i * 3 + 2] > 0) --_palette[i * 3 + 2]; } g_system->getPaletteManager()->setPalette(_palette, 0, 256); g_system->updateScreen(); g_system->delayMillis(10); } } } // End of namespace Chewy