scummvm/engines/groovie/vdx.cpp
2011-04-08 00:17:59 +09:30

571 lines
16 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$
*
*/
#include "groovie/vdx.h"
#include "groovie/graphics.h"
#include "groovie/groovie.h"
#include "groovie/lzss.h"
#include "common/debug-channels.h"
#include "audio/mixer.h"
#include "audio/decoders/raw.h"
#define TILE_SIZE 4 // Size of each tile on the image: only ever seen 4 so far
#define VDX_IDENT 0x9267 // 37479
namespace Groovie {
VDXPlayer::VDXPlayer(GroovieEngine *vm) :
VideoPlayer(vm), _origX(0), _origY(0), _flagOnePrev(false),
_fg(&_vm->_graphicsMan->_foreground), _bg(&_vm->_graphicsMan->_background) {
}
VDXPlayer::~VDXPlayer() {
//delete _audioStream;
}
void VDXPlayer::resetFlags() {
_flagOnePrev = false;
}
void VDXPlayer::setOrigin(int16 x, int16 y) {
_origX = x;
_origY = y;
}
uint16 VDXPlayer::loadInternal() {
if (DebugMan.isDebugChannelEnabled(kGroovieDebugVideo) ||
DebugMan.isDebugChannelEnabled(kGroovieDebugAll)) {
int8 i;
debugN(1, "Groovie::VDX: New VDX: bitflags are ");
for (i = 15; i >= 0; i--) {
debugN(1, "%d", _flags & (1 << i)? 1 : 0);
if (i % 4 == 0) {
debugN(1, " ");
}
}
debug(1, " <- 0 ");
}
// Flags:
// - 1 Puzzle piece? Skip palette, don't redraw full screen, draw still to b/ack buffer
// - 2 Transparent colour is 0xFF
// - 5 Skip still chunks
// - 7
// - 8 Just show the first frame
// - 9 Start a palette fade in
_flagZero = ((_flags & (1 << 0)) != 0);
_flagOne = ((_flags & (1 << 1)) != 0);
_flag2Byte = (_flags & (1 << 2)) ? 0xFF : 0x00;
_flagThree = ((_flags & (1 << 3)) != 0);
_flagFour = ((_flags & (1 << 4)) != 0);
_flagFive = ((_flags & (1 << 5)) != 0);
_flagSix = ((_flags & (1 << 6)) != 0);
_flagSeven = ((_flags & (1 << 7)) != 0);
_flagEight = ((_flags & (1 << 8)) != 0);
_flagNine = ((_flags & (1 << 9)) != 0);
// Enable highspeed if we're not obeying fps, and not marked as special
// This will be disabled in chunk audio if we're actually an audio vdx
if ( _vm->_modeSpeed == kGroovieSpeediOS || (_vm->_modeSpeed == kGroovieSpeedTweaked && ((_flags & (1 << 15)) == 0)))
setOverrideSpeed(true);
if (_flagOnePrev && !_flagOne && !_flagEight) {
_flagSeven = true;
}
// Save _flagOne for the next video
_flagOnePrev = _flagOne;
//_flagTransparent = _flagOne;
_flagFirstFrame = _flagEight;
//_flagSkipPalette = _flagSeven;
_flagSkipPalette = false;
//_flagSkipStill = _flagFive || _flagSeven;
//_flagUpdateStill = _flagNine || _flagSix;
// Begin reading the file
debugC(1, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: Playing video");
if (_file->readUint16LE() != VDX_IDENT) {
error("Groovie::VDX: This does not appear to be a 7th guest VDX file");
return 0;
} else {
debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: VDX file identified correctly");
}
uint16 tmp;
// Skip unknown data: 6 bytes, ref Martine
tmp = _file->readUint16LE();
debugC(2, kGroovieDebugVideo | kGroovieDebugUnknown | kGroovieDebugAll, "Groovie::VDX: Martine1 = 0x%04X", tmp);
tmp = _file->readUint16LE();
debugC(2, kGroovieDebugVideo | kGroovieDebugUnknown | kGroovieDebugAll, "Groovie::VDX: Martine2 = 0x%04X", tmp);
tmp = _file->readUint16LE();
debugC(2, kGroovieDebugVideo | kGroovieDebugUnknown | kGroovieDebugAll, "Groovie::VDX: Martine3 (FPS?) = %d", tmp);
return tmp;
}
bool VDXPlayer::playFrameInternal() {
byte currRes = 0x80;
Common::ReadStream *vdxData = 0;
while (currRes == 0x80) {
currRes = _file->readByte();
// Skip unknown data: 1 byte, ref Edward
byte tmp = _file->readByte();
uint32 compSize = _file->readUint32LE();
uint8 lengthmask = _file->readByte();
uint8 lengthbits = _file->readByte();
if (_file->eos())
break;
debugC(5, kGroovieDebugVideo | kGroovieDebugUnknown | kGroovieDebugAll, "Groovie::VDX: Edward = 0x%04X", tmp);
// Read the chunk data and decompress if needed
if (compSize)
vdxData = _file->readStream(compSize);
if (lengthmask && lengthbits) {
Common::ReadStream *decompData = new LzssReadStream(vdxData, lengthmask, lengthbits);
delete vdxData;
vdxData = decompData;
}
// Use the current chunk
switch (currRes) {
case 0x00:
debugC(6, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: Replay frame");
break;
case 0x20:
debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: Still frame");
getStill(vdxData);
break;
case 0x25:
debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: Animation frame");
getDelta(vdxData);
break;
case 0x80:
debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: Sound resource");
chunkSound(vdxData);
break;
default:
error("Groovie::VDX: Invalid resource type: %d", currRes);
}
delete vdxData;
vdxData = 0;
}
// Wait until the current frame can be shown
if (!DebugMan.isDebugChannelEnabled(kGroovieDebugFast)) {
waitFrame();
}
// TODO: Move it to a better place
// Update the screen
if (currRes == 0x25) {
//if (_flagSeven) {
//_vm->_graphicsMan->mergeFgAndBg();
//}
_vm->_graphicsMan->updateScreen(_bg);
}
// Report the end of the video if we reached the end of the file or if we
// just wanted to play one frame.
if (_file->eos() || _flagFirstFrame) {
_origX = _origY = 0;
return 1;
} else {
return 0;
}
}
static const uint16 vdxBlockMapLookup[] = {
0xc800, 0xec80, 0xfec8, 0xffec, 0xfffe, 0x3100, 0x7310, 0xf731, 0xff73, 0xfff7, 0x6c80, 0x36c8, 0x136c, 0x6310, 0xc631, 0x8c63,
0xf000, 0xff00, 0xfff0, 0x1111, 0x3333, 0x7777, 0x6666, 0xcccc, 0x0ff0, 0x00ff, 0xffcc, 0x0076, 0xff33, 0x0ee6, 0xccff, 0x6770,
0x33ff, 0x6ee0, 0x4800, 0x2480, 0x1248, 0x0024, 0x0012, 0x2100, 0x4210, 0x8421, 0x0042, 0x0084, 0xf888, 0x0044, 0x0032, 0x111f,
0x22e0, 0x4c00, 0x888f, 0x4470, 0x2300, 0xf111, 0x0e22, 0x00c4, 0xf33f, 0xfccf, 0xff99, 0x99ff, 0x4444, 0x2222, 0xccee, 0x7733,
0x00f8, 0x00f1, 0x00bb, 0x0cdd, 0x0f0f, 0x0f88, 0x13f1, 0x19b3, 0x1f80, 0x226f, 0x27ec, 0x3077, 0x3267, 0x37e4, 0x38e3, 0x3f90,
0x44cf, 0x4cd9, 0x4c99, 0x5555, 0x603f, 0x6077, 0x6237, 0x64c9, 0x64cd, 0x6cd9, 0x70ef, 0x0f00, 0x00f0, 0x0000, 0x4444, 0x2222
};
void VDXPlayer::getDelta(Common::ReadStream *in) {
uint16 k, l;
// Get the size of the local palette
uint16 palSize = in->readUint16LE();
// Load the palette if it isn't empty
if (palSize) {
uint16 palBitField[16];
// Load the bit field
for (l = 0; l < 16; l++) {
palBitField[l] = in->readUint16LE();
}
// Load the actual palette
for (l = 0; l < 16; l++) {
int flag = 1 << 15;
for (uint16 j = 0; j < 16; j++) {
int palIndex = (l * 16) + j;
if (flag & palBitField[l]) {
for (k = 0; k < 3; k++) {
_palBuf[(palIndex * 3) + k] = in->readByte();
}
}
flag = flag >> 1;
}
}
// Apply the palette
if (!_flagSeven) {
//if (!_flagSix && !_flagSeven) {
setPalette(_palBuf);
}
}
uint8 currOpCode = in->readByte();
uint8 param1, param2, param3;
uint16 currentLine = 0;
uint32 offset = 0;
while (!in->eos()) {
byte colours[16];
if (currOpCode < 0x60) {
param1 = in->readByte();
param2 = in->readByte();
expandColourMap(colours, vdxBlockMapLookup[currOpCode], param1, param2);
decodeBlockDelta(offset, colours, 640);
offset += TILE_SIZE;
} else if (currOpCode > 0x7f) {
param1 = in->readByte();
param2 = in->readByte();
param3 = in->readByte();
expandColourMap(colours, (param1 << 8) + currOpCode, param2, param3);
decodeBlockDelta(offset, colours, 640);
offset += TILE_SIZE;
} else switch (currOpCode) {
case 0x60: /* Fill tile with the 16 colours given as parameters */
for (l = 0; l < 16; l++) {
colours[l] = in->readByte();
}
decodeBlockDelta(offset, colours, 640);
offset += TILE_SIZE;
break;
case 0x61: /* Skip to the end of this line, next block is start of next */
/* Note this is used at the end of EVERY line */
currentLine++;
offset = currentLine * TILE_SIZE * 640;
break;
case 0x62:
case 0x63:
case 0x64:
case 0x65:
case 0x66:
case 0x67:
case 0x68:
case 0x69:
case 0x6a:
case 0x6b: /* Skip next param1 blocks (within line) */
offset += (currOpCode - 0x62) * TILE_SIZE;
break;
case 0x6c:
case 0x6d:
case 0x6e:
case 0x6f:
case 0x70:
case 0x71:
case 0x72:
case 0x73:
case 0x74:
case 0x75: /* Next param1 blocks are filled with colour param2 */
param1 = currOpCode - 0x6b;
param2 = in->readByte();
for (l = 0; l < 16; l++) {
colours[l] = param2;
}
for (k = 0; k < param1; k++) {
decodeBlockDelta(offset, colours, 640);
offset += TILE_SIZE;
}
break;
case 0x76:
case 0x77:
case 0x78:
case 0x79:
case 0x7a:
case 0x7b:
case 0x7c:
case 0x7d:
case 0x7e:
case 0x7f: /* Next bytes contain colours to fill the next param1 blocks in the current line*/
param1 = currOpCode - 0x75;
for (k = 0; k < param1; k++) {
param2 = in->readByte();
for (l = 0; l < 16; l++) {
colours[l] = param2;
}
decodeBlockDelta(offset, colours, 640);
offset += TILE_SIZE;
}
break;
default:
error("Groovie::VDX: Broken somehow");
}
currOpCode = in->readByte();
}
}
void VDXPlayer::getStill(Common::ReadStream *in) {
uint16 numXTiles = in->readUint16LE();
debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: numXTiles=%d", numXTiles);
uint16 numYTiles = in->readUint16LE();
debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: numYTiles=%d", numYTiles);
// It's skipped in the original:
uint16 colourDepth = in->readUint16LE();
debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: colourDepth=%d", colourDepth);
uint16 imageWidth = TILE_SIZE * numXTiles;
uint8 mask = 0;
byte *buf;
if (_flagOne) {
// Paint to the foreground
buf = (byte *)_fg->getBasePtr(0, 0);
if (_flag2Byte) {
mask = 0xff;
} else {
mask = 0;
}
// TODO: Verify this is the right procedure. Couldn't find it on the
// disassembly, but it's required to work properly
_flagFirstFrame = true;
} else {
// Paint to the background
buf = (byte *)_bg->getBasePtr(0, 0);
}
// Read the palette
in->read(_palBuf, 3 * 256);
if (_flagSeven) {
_flagFive = true;
}
// Skip the frame when flag 5 is set, unless flag 1 is set
if (!_flagFive || _flagOne) {
byte colours[16];
for (uint16 j = 0; j < numYTiles; j++) {
byte *currentTile = buf + j * TILE_SIZE * imageWidth;
for (uint16 i = numXTiles; i; i--) {
uint8 colour1 = in->readByte();
uint8 colour0 = in->readByte();
uint16 colourMap = in->readUint16LE();
expandColourMap(colours, colourMap, colour1, colour0);
decodeBlockStill(currentTile, colours, 640, mask);
currentTile += TILE_SIZE;
}
}
// Apply the palette
if (_flagNine) {
// Flag 9 starts a fade in
fadeIn(_palBuf);
} else {
if (!_flagOne && !_flagSeven) {
// Actually apply the palette
setPalette(_palBuf);
}
}
if (!_flagOne) {
_vm->_graphicsMan->updateScreen(_bg);
}
/*
if (_flagSix) {
if (_flagOne) {
_vm->_graphicsMan->updateScreen(_fg);
} else {
_vm->_graphicsMan->updateScreen(_bg);
}
_flagSix = 0;
}
*/
} else {
// Skip the remaining data
debugC(10, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: Skipping still frame");
while (!in->eos()) {
in->readByte();
}
}
}
void VDXPlayer::expandColourMap(byte *out, uint16 colourMap, uint8 colour1, uint8 colour0) {
// It's a bit faster to start from the end
out += 16;
for (int i = 16; i; i--) {
// Set the corresponding colour
// The following is an optimized version of:
// *--out = (colourMap & 1) ? colour1 : colour0;
uint8 selector = -(colourMap & 1);
*--out = (selector & colour1) | (~selector & colour0);
// Update the flag map to test the next colour
colourMap >>= 1;
}
}
void VDXPlayer::decodeBlockStill(byte *buf, byte *colours, uint16 imageWidth, uint8 mask) {
assert(TILE_SIZE == 4);
for (int y = TILE_SIZE; y; y--) {
if (_flagOne) {
// TODO: optimize with bit logic?
for (int x = 0; x < TILE_SIZE; x++) {
// 0xff pixels don't modify the buffer
if (*colours != 0xff) {
// Write the colour
*buf = *colours | mask;
// Note: if the mask is 0, it paints the image
// else, it paints the image's mask using 0xff
}
// Point to the next colour
colours++;
// Point to the next pixel
buf++;
}
// Point to the start of the next line
buf += imageWidth - TILE_SIZE;
} else {
*((uint32 *)buf) = *((uint32 *)colours);
colours += 4;
// Point to the start of the next line
buf += imageWidth;
}
}
}
void VDXPlayer::decodeBlockDelta(uint32 offset, byte *colours, uint16 imageWidth) {
assert(TILE_SIZE == 4);
byte *dest;
// TODO: Verify just the else block is required
//if (_flagOne) {
// Paint to the foreground
//dest = (byte *)_fg->getBasePtr(0, 0) + offset;
//} else {
dest = (byte *)_bg->getBasePtr(0, 0) + offset;
//}
// Move the pointers to the beginning of the current block
int32 blockOff = _origX + _origY * imageWidth;
dest += blockOff;
byte *fgBuf = 0;
if (_flagSeven) {
fgBuf = (byte *)_fg->getBasePtr(0, 0) + offset + blockOff;
//byte *bgBuf = (byte *)_bg->getBasePtr(0, 0) + offset + blockOff;
}
for (int y = TILE_SIZE; y; y--) {
if (_flagSeven) {
// Paint mask
for (int x = 0; x < TILE_SIZE; x++) {
// TODO: this can probably be optimized with bit logic
if (fgBuf[x] != 0xff) {
if (*colours == 0xff) {
dest[x] = fgBuf[x];
} else {
dest[x] = *colours;
}
}
colours++;
}
fgBuf += imageWidth;
} else {
// Paint directly
*((uint32 *)dest) = *((uint32 *)colours);
colours += 4;
}
// Move to the next line
dest += imageWidth;
}
}
void VDXPlayer::chunkSound(Common::ReadStream *in) {
if (getOverrideSpeed())
setOverrideSpeed(false);
if (!_audioStream) {
_audioStream = Audio::makeQueuingAudioStream(22050, false);
Audio::SoundHandle sound_handle;
g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &sound_handle, _audioStream);
}
byte *data = (byte *)malloc(60000);
int chunksize = in->read(data, 60000);
if (!DebugMan.isDebugChannelEnabled(kGroovieDebugFast)) {
_audioStream->queueBuffer(data, chunksize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
}
}
void VDXPlayer::fadeIn(uint8 *targetpal) {
// Don't do anything if we're asked to skip palette changes
if (_flagSkipPalette)
return;
// TODO: Is it required? If so, move to an appropiate place
// Copy the foreground to the background
memcpy((byte *)_vm->_graphicsMan->_foreground.getBasePtr(0, 0), (byte *)_vm->_graphicsMan->_background.getBasePtr(0, 0), 640 * 320);
// Start a fadein
_vm->_graphicsMan->fadeIn(targetpal);
// Show the background
_vm->_graphicsMan->updateScreen(_bg);
}
void VDXPlayer::setPalette(uint8 *palette) {
if (_flagSkipPalette)
return;
debugC(7, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: Setting palette");
_syst->getPaletteManager()->setPalette(palette, 0, 256);
}
} // End of Groovie namespace