307 lines
9.4 KiB
C++
307 lines
9.4 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.
|
|
*
|
|
* $URL$
|
|
* $Id$
|
|
*
|
|
*/
|
|
|
|
// fopen() and friends so we can dump frames and stuff to disk, for debugging
|
|
// #define FORBIDDEN_SYMBOL_ALLOW_ALL
|
|
|
|
#include "sci/sci.h"
|
|
#include "sci/engine/state.h"
|
|
#include "sci/graphics/screen.h"
|
|
#include "sci/graphics/robot.h"
|
|
#include "sci/graphics/palette.h"
|
|
#include "sci/sound/audio.h"
|
|
|
|
#include "graphics/surface.h"
|
|
|
|
#include "sound/audiostream.h"
|
|
#include "sound/mixer.h"
|
|
|
|
#include "common/file.h"
|
|
#include "common/system.h"
|
|
#include "common/memstream.h"
|
|
|
|
namespace Sci {
|
|
|
|
// Some non technical information on robot files, from an interview with
|
|
// Greg Tomko-Pavia of Sierra On-Line
|
|
// Taken from http://anthonylarme.tripod.com/phantas/phintgtp.html
|
|
//
|
|
// (...) What we needed was a way of playing video, but have it blend into
|
|
// normal room art instead of occupying its own rectangular area. Room art
|
|
// consists of a background pic overlaid with various animating cels
|
|
// (traditional lingo: sprites). The cels each have a priority that determines
|
|
// who is on top and who is behind in the drawing order. Cels are read from
|
|
// *.v56 files (another proprietary format). A Robot is video frames with
|
|
// transparent background including priority and x,y information. Thus, it is
|
|
// like a cel, except it comes from an RBT - not a v56. Because it blends into
|
|
// our graphics engine, it looks just like a part of the room. A RBT can move
|
|
// around the screen and go behind other objects. (...)
|
|
|
|
#ifdef ENABLE_SCI32
|
|
GfxRobot::GfxRobot(ResourceManager *resMan, GfxScreen *screen, GfxPalette *palette)
|
|
: _resMan(resMan), _screen(screen), _palette(palette) {
|
|
_resourceId = -1;
|
|
_x = _y = 0;
|
|
}
|
|
|
|
GfxRobot::~GfxRobot() {
|
|
delete[] _resourceData;
|
|
delete[] _imageStart;
|
|
delete[] _audioStart;
|
|
delete[] _audioLen;
|
|
}
|
|
|
|
void GfxRobot::init(GuiResourceId resourceId, uint16 x, uint16 y) {
|
|
char fileName[10];
|
|
uint32 fileSize;
|
|
|
|
// resourceId = 1305; // debug
|
|
|
|
_resourceId = resourceId;
|
|
_x = x;
|
|
_y = y;
|
|
_curFrame = 0;
|
|
sprintf(fileName, "%d.rbt", _resourceId);
|
|
|
|
Common::File robotFile;
|
|
if (robotFile.open(fileName)) {
|
|
_resourceData = new byte[robotFile.size()];
|
|
robotFile.read(_resourceData, robotFile.size());
|
|
fileSize = robotFile.size();
|
|
robotFile.close();
|
|
} else {
|
|
warning("Unable to open robot file %s", fileName);
|
|
return;
|
|
}
|
|
|
|
_frameCount = READ_LE_UINT16(_resourceData + 14);
|
|
|
|
// There is another value in the header, at offset 0x12, which
|
|
// equals this value plus 14 (audio header size) in the cases
|
|
// I've seen so far. Dunno which one to use, really.
|
|
_audioSize = READ_LE_UINT16(_resourceData + 60);
|
|
|
|
//_frameSize = READ_LE_UINT32(_resourceData + 34);
|
|
_hasSound = (_resourceData[25] != 0);
|
|
|
|
_palOffset = 60;
|
|
|
|
// Some robot files have sound, which doesn't start from frame 0
|
|
// (e.g. Phantasmagoria, robot 1305)
|
|
// Yes, strangely, the word at offset 10 is non-zero if it DOESN'T have a preload
|
|
// in that case, the word is equal to that value which would otherwise be stored
|
|
// in the audio header (i.e. _audioSize above)
|
|
if (_hasSound && READ_LE_UINT16(_resourceData + 10) == 0)
|
|
_palOffset += READ_LE_UINT32(_resourceData + 60) + 14;
|
|
|
|
getFrameOffsets();
|
|
assert(_imageStart[_frameCount] == fileSize);
|
|
|
|
setPalette();
|
|
|
|
debug("Robot %d, %d frames, sound: %s\n", resourceId, _frameCount, _hasSound ? "yes" : "no");
|
|
}
|
|
|
|
void GfxRobot::setPalette() {
|
|
byte *paletteData = _resourceData + _palOffset;
|
|
uint16 paletteSize = READ_LE_UINT16(_resourceData + 16);
|
|
|
|
Palette resourcePal;
|
|
|
|
byte robotPal[256 * 4];
|
|
int startIndex = READ_LE_UINT16(paletteData + 25);
|
|
int colorCount = READ_LE_UINT16(paletteData + 29);
|
|
|
|
warning("%d palette entries starting at %d", colorCount, startIndex);
|
|
|
|
_palette->createFromData(paletteData, paletteSize, &resourcePal);
|
|
|
|
for (int i = 0; i < 256; ++i) {
|
|
_savedPal[i * 4 + 0] = _palette->_sysPalette.colors[i].r;
|
|
_savedPal[i * 4 + 1] = _palette->_sysPalette.colors[i].g;
|
|
_savedPal[i * 4 + 2] = _palette->_sysPalette.colors[i].b;
|
|
_savedPal[i * 4 + 3] = 0;
|
|
}
|
|
|
|
memcpy(robotPal, _savedPal, sizeof(_savedPal));
|
|
|
|
for (int i = 0; i < colorCount; ++i) {
|
|
int index = i + startIndex;
|
|
robotPal[index * 4 + 0] = resourcePal.colors[index].r;
|
|
robotPal[index * 4 + 1] = resourcePal.colors[index].g;
|
|
robotPal[index * 4 + 2] = resourcePal.colors[index].b;
|
|
robotPal[index * 4 + 3] = 0;
|
|
}
|
|
|
|
g_system->setPalette(robotPal, 0, 256);
|
|
}
|
|
|
|
void GfxRobot::drawNextFrame() {
|
|
int width, height;
|
|
|
|
byte *pixels = assembleVideoFrame(_curFrame);
|
|
getFrameDimensions(_curFrame, width, height);
|
|
g_system->copyRectToScreen(pixels, width, _x, _y, width, height * getFrameScale(_curFrame) / 100);
|
|
g_system->updateScreen();
|
|
g_system->delayMillis(100);
|
|
delete[] pixels;
|
|
|
|
_curFrame++;
|
|
|
|
if (_curFrame == _frameCount) {
|
|
// End of robot video, restore palette
|
|
g_system->setPalette(_savedPal, 0, 256);
|
|
_resourceId = -1;
|
|
}
|
|
}
|
|
|
|
void GfxRobot::getFrameOffsets() {
|
|
int *audioEnd = new int[_frameCount];
|
|
int *videoEnd = new int[_frameCount];
|
|
|
|
for (int i = 0; i < _frameCount; ++i) {
|
|
videoEnd[i] = READ_LE_UINT16(_resourceData + _palOffset + 1200 + i * 2);
|
|
audioEnd[i] = READ_LE_UINT16(_resourceData + _palOffset + 1200 + _frameCount * 2 + i * 2);
|
|
}
|
|
|
|
int frameDataOffset = _palOffset + 0x4b0 + 0x400 + 0x200 + _frameCount * 4;
|
|
|
|
// Pad to nearest 2 kilobytes
|
|
if (frameDataOffset & 0x7ff)
|
|
frameDataOffset = (frameDataOffset & ~0x7ff)+0x800;
|
|
|
|
_imageStart = new uint32[_frameCount + 1];
|
|
_audioStart = new uint32[_frameCount];
|
|
_audioLen = new uint32[_frameCount];
|
|
|
|
_imageStart[0] = frameDataOffset;
|
|
|
|
// Plus one so we can assert on this in the calling routine
|
|
// The last one should point to end-of-file, unless I'm misunderstanding something
|
|
for (int i = 1; i < _frameCount + 1; ++i)
|
|
_imageStart[i] = _imageStart[i - 1] + audioEnd[i - 1];
|
|
for (int i = 0; i < _frameCount; ++i)
|
|
_audioStart[i] = _imageStart[i] + videoEnd[i];
|
|
for (int i = 0; i < _frameCount; ++i)
|
|
_audioLen[i] = _imageStart[i + 1] - _audioStart[i];
|
|
|
|
delete[] audioEnd;
|
|
delete[] videoEnd;
|
|
}
|
|
|
|
byte *GfxRobot::assembleVideoFrame(int frame) {
|
|
byte *videoData = _resourceData + _imageStart[frame];
|
|
int frameWidth = READ_LE_UINT16(videoData + 4);
|
|
int frameHeight = READ_LE_UINT16(videoData + 6);
|
|
int frameFragments = READ_LE_UINT16(videoData + 18);
|
|
|
|
int decompressedSize = 0;
|
|
|
|
DecompressorLZS lzs;
|
|
|
|
videoData = _resourceData + _imageStart[frame] + 24;
|
|
|
|
for (int i = 0; i < frameFragments; ++i) {
|
|
int fragmentCompressed = READ_LE_UINT32(videoData);
|
|
int fragmentUncompressed = READ_LE_UINT32(videoData + 4);
|
|
|
|
decompressedSize += fragmentUncompressed;
|
|
videoData += 10 + fragmentCompressed;
|
|
}
|
|
|
|
assert(decompressedSize == (frameWidth * frameHeight) * getFrameScale(frame) / 100);
|
|
|
|
byte *output = new byte[decompressedSize];
|
|
int assemblePtr = 0;
|
|
|
|
videoData = _resourceData + _imageStart[frame] + 24;
|
|
|
|
for (int i = 0; i < frameFragments; ++i) {
|
|
int fragmentCompressed = READ_LE_UINT32(videoData);
|
|
int fragmentUncompressed = READ_LE_UINT32(videoData + 4);
|
|
uint16 compressionType = READ_LE_UINT16(videoData + 8);
|
|
|
|
switch (compressionType) {
|
|
case 0: {
|
|
Common::MemoryReadStream compressedFrame(videoData + 10, fragmentCompressed, DisposeAfterUse::NO);
|
|
|
|
lzs.unpack(&compressedFrame, output + assemblePtr, fragmentCompressed, fragmentUncompressed);
|
|
assemblePtr += fragmentUncompressed;
|
|
break;
|
|
}
|
|
case 2: // Untested
|
|
memcpy(output + assemblePtr, videoData + 10, fragmentCompressed);
|
|
assemblePtr += fragmentUncompressed;
|
|
break;
|
|
}
|
|
|
|
videoData += 10 + fragmentCompressed;
|
|
}
|
|
|
|
assert(assemblePtr == decompressedSize);
|
|
|
|
// FILE *f = fopen("/tmp/flap", "w");
|
|
// fwrite(output, 1, decompressedSize, f);
|
|
// fclose(f);
|
|
// exit(1);
|
|
|
|
return output;
|
|
}
|
|
|
|
void GfxRobot::getFrameDimensions(int frame, int &width, int &height) {
|
|
byte *videoData = _resourceData + _imageStart[frame];
|
|
|
|
width = READ_LE_UINT16(videoData + 4);
|
|
height = READ_LE_UINT16(videoData + 6);
|
|
}
|
|
|
|
void GfxRobot::getFrameRect(int frame, Common::Rect &rect) {
|
|
byte *videoData = _resourceData + _imageStart[frame];
|
|
|
|
int x = READ_LE_UINT16(videoData + 8);
|
|
int y = READ_LE_UINT16(videoData + 10);
|
|
int w = READ_LE_UINT16(videoData + 12);
|
|
int h = READ_LE_UINT16(videoData + 14);
|
|
|
|
rect = Common::Rect(x, y, x + w, y + h);
|
|
}
|
|
|
|
int GfxRobot::getFrameScale(int frame) {
|
|
byte *videoData = _resourceData + _imageStart[frame];
|
|
return videoData[3];
|
|
}
|
|
|
|
void GfxRobot::playAudio() {
|
|
if (_hasSound) {
|
|
Audio::SoundHandle _audioHandle;
|
|
Audio::AudioStream *audioStream = g_sci->_audio->getRobotAudioStream(_resourceData);
|
|
g_system->getMixer()->playStream(Audio::Mixer::kSpeechSoundType, &_audioHandle, audioStream);
|
|
}
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
} // End of namespace Sci
|