mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-28 20:55:19 +00:00
1dbf8d73d5
Use of global vars is what prevents RTL from working in Tinsel (and probably in other engines). More specifically, the fact that many global vars are not explicitly inited when the engine is (re)launched. svn-id: r54262
1183 lines
26 KiB
C++
1183 lines
26 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$
|
|
*
|
|
* The movie player.
|
|
*/
|
|
|
|
#include "tinsel/tinsel.h"
|
|
#include "tinsel/background.h"
|
|
#include "tinsel/bmv.h"
|
|
#include "tinsel/cliprect.h"
|
|
#include "tinsel/config.h"
|
|
#include "tinsel/dw.h"
|
|
#include "tinsel/events.h"
|
|
#include "tinsel/font.h"
|
|
#include "tinsel/graphics.h"
|
|
#include "tinsel/handle.h"
|
|
#include "tinsel/multiobj.h"
|
|
#include "tinsel/sched.h"
|
|
#include "tinsel/strres.h"
|
|
#include "tinsel/text.h"
|
|
#include "tinsel/timers.h"
|
|
#include "tinsel/tinlib.h"
|
|
#include "tinsel/tinsel.h"
|
|
|
|
#include "sound/decoders/raw.h"
|
|
|
|
namespace Tinsel {
|
|
|
|
//----------------- LOCAL DEFINES ----------------------------
|
|
|
|
#define BMOVIE_EXTENSION ".bmv"
|
|
|
|
#define SZ_C_BLOB 65
|
|
#define SZ_U_BLOB 128
|
|
|
|
#define BLANK_SOUND 0x0 // for 16 bit silence
|
|
|
|
#define PT_A 20 // Number of times PT_B may be reached
|
|
#define PT_B 6
|
|
|
|
|
|
#define SLOT_SIZE (25*1024)
|
|
//#define NUM_SLOTS 168
|
|
#define NUM_SLOTS 122 // -> ~ 3MB
|
|
|
|
|
|
#define PREFETCH (NUM_SLOTS/2) // For initial test
|
|
|
|
#define ADVANCE_SOUND 18 // 1 1/2 seconds
|
|
#define SUBSEQUENT_SOUND 6 // 1/2 second
|
|
|
|
|
|
|
|
// PACKET TYPE IDs & FLAGS
|
|
|
|
#define CD_SLOT_NOP 0x00 // Skip to next slot
|
|
#define CD_LE_FIN 0x01 // End of movie
|
|
#define CD_PDELTA 0x02 // Image compressed to previous one
|
|
#define CD_SDELTA 0x03 // Image self-compressed
|
|
|
|
#define BIT0 0x01
|
|
|
|
#define CD_XSCR 0x04 // Screen has a scroll offset
|
|
#define CD_CMAP 0x08 // Colour map is included
|
|
#define CD_CMND 0x10 // Command is included
|
|
#define CD_AUDIO 0x20 // Audio data is included
|
|
#define CD_EXTEND 0x40 // Extended modes "A"-"z"
|
|
#define CD_PRINT 0x80 // goes in conjunction with CD_CMD
|
|
|
|
// Data field sizes
|
|
#define sz_XSCR_pkt 2
|
|
#define sz_CMAP_pkt 0x300
|
|
#define sz_CMD_TALK_pkt 10
|
|
#define sz_CMD_PRINT_pkt 8
|
|
#define sz_AUDIO_pkt 3675
|
|
|
|
|
|
struct TALK_CMD {
|
|
short x;
|
|
short y;
|
|
short stringId;
|
|
unsigned char duration;
|
|
char r; // may be b!
|
|
char g;
|
|
char b; // may be r!
|
|
};
|
|
|
|
struct PRINT_CMD {
|
|
int16 x;
|
|
int16 y;
|
|
int16 stringId;
|
|
unsigned char duration;
|
|
unsigned char fontId;
|
|
};
|
|
|
|
|
|
//----------------- LOCAL GLOBAL DATA ------------------------
|
|
|
|
static const uint16 Au_DecTable[16] = {16512, 8256, 4128, 2064, 1032, 516, 258, 192,
|
|
129, 88, 64, 56, 48, 40, 36, 32};
|
|
|
|
//---------------- DECOMPRESSOR FUNCTIONS --------------------
|
|
|
|
#define SCREEN_WIDE 640
|
|
#define SCREEN_HIGH 429
|
|
#define SAM_P_BLOB (32 * 2)
|
|
|
|
#define ROR(x,v) x = ((x >> (v%32)) | (x << (32 - (v%32))))
|
|
#define ROL(x,v) x = ((x << (v%32)) | (x >> (32 - (v%32))))
|
|
#define NEXT_BYTE(v) v = (forwardDirection ? v + 1 : v - 1)
|
|
|
|
static void PrepBMV(byte *ScreenBeg, const byte *sourceData, int length, short deltaFetchDisp) {
|
|
uint8 NibbleHi = 0;
|
|
uint32 edx = length;
|
|
int32 ebx = deltaFetchDisp;
|
|
const byte *src;
|
|
byte *dst, *endDst;
|
|
|
|
const bool forwardDirection = (deltaFetchDisp <= -SCREEN_WIDE) || (deltaFetchDisp >= 0);
|
|
if (forwardDirection) {
|
|
// Forward decompression
|
|
src = sourceData;
|
|
dst = ScreenBeg;
|
|
endDst = ScreenBeg + SCREEN_WIDE * SCREEN_HIGH;
|
|
} else {
|
|
src = sourceData + length - 1;
|
|
dst = ScreenBeg + SCREEN_WIDE * SCREEN_HIGH - 1;
|
|
endDst = ScreenBeg - 1;
|
|
}
|
|
|
|
bool firstLoop, flag;
|
|
|
|
int loopCtr = 0;
|
|
do {
|
|
uint32 eax = 0;
|
|
uint32 bitshift = 0;
|
|
flag = false;
|
|
|
|
if ((loopCtr == 0) || (edx == 4)) {
|
|
// Get the next hi,lo nibble
|
|
eax = (eax & 0xffffff00) | *src;
|
|
firstLoop = true;
|
|
} else {
|
|
// Get the high nibble
|
|
eax = (eax & 0xffffff00) | NibbleHi;
|
|
firstLoop = false;
|
|
}
|
|
|
|
// Is lo nibble '00xx'?
|
|
if ((eax & 0xC) == 0) {
|
|
for (;;) {
|
|
//@_rDN_Lp_1:
|
|
// Only execute this bit first the first time into the loop
|
|
if (!firstLoop) {
|
|
ROR(eax, 2);
|
|
bitshift += 2;
|
|
eax = (eax & 0xffffff00) | *src;
|
|
|
|
if ((eax & 0xC) != 0)
|
|
break;
|
|
}
|
|
firstLoop = false;
|
|
|
|
//@_rD2nd_1:
|
|
ROR(eax, 2); // Save bi-bit into hi 2 bits
|
|
bitshift += 2; // and increase bit-shifter
|
|
// Shift another 2 bits to get hi nibble
|
|
eax = (eax & 0xffffff00) | ((eax & 0xff) >> 2);
|
|
NEXT_BYTE(src);
|
|
|
|
if ((eax & 0xC) != 0) {
|
|
flag = true;
|
|
ROL(eax, bitshift);
|
|
break;
|
|
}
|
|
}
|
|
} else if (loopCtr != 0) {
|
|
flag = edx != 4;
|
|
}
|
|
|
|
if (flag) {
|
|
//@_rdNum__1:
|
|
edx = 4; // offset rDNum_Lo ; Next nibble is a 'lo'
|
|
} else {
|
|
// @_rDNum_1
|
|
NibbleHi = ((uint8)eax) >> 4;
|
|
edx = 0; // offset rDNum_Hi ; Next nibble is a 'hi' (reserved)
|
|
eax &= 0xffffff0f;
|
|
NEXT_BYTE(src);
|
|
ROL(eax, bitshift);
|
|
}
|
|
//rDN_1:
|
|
//@_rD_or_R:
|
|
bool actionFlag = (eax & 1) != 0;
|
|
eax >>= 1;
|
|
int byteLen = eax - 1;
|
|
|
|
// Move to next loop index
|
|
++loopCtr;
|
|
if (loopCtr == 4)
|
|
loopCtr = 1;
|
|
|
|
if (actionFlag) {
|
|
// Adjust loopCtr to fall into the correct processing case
|
|
loopCtr = loopCtr % 3 + 1;
|
|
}
|
|
|
|
switch (loopCtr) {
|
|
case 1: {
|
|
// @_rDelta:
|
|
const byte *saved_src = src; // Save the source pointer
|
|
src = dst + ebx; // Point it to existing data
|
|
|
|
while (byteLen > 0) {
|
|
*dst = *src;
|
|
NEXT_BYTE(src);
|
|
NEXT_BYTE(dst);
|
|
--byteLen;
|
|
}
|
|
|
|
src = saved_src;
|
|
break;
|
|
}
|
|
|
|
case 2:
|
|
// @_rRaw
|
|
// Copy data from source to dest
|
|
while (byteLen > 0) {
|
|
*dst = *src;
|
|
NEXT_BYTE(src);
|
|
NEXT_BYTE(dst);
|
|
--byteLen;
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
// @_rRun
|
|
// Repeating run of data
|
|
eax = forwardDirection ? *(dst - 1) : *(dst + 1);
|
|
|
|
while (byteLen > 0) {
|
|
*dst = (uint8)eax;
|
|
NEXT_BYTE(dst);
|
|
--byteLen;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} while (dst != endDst);
|
|
}
|
|
|
|
void BMVPlayer::InitBMV(byte *memoryBuffer) {
|
|
// Clear the two extra 'off-screen' rows
|
|
memset(memoryBuffer, 0, SCREEN_WIDE);
|
|
memset(memoryBuffer + SCREEN_WIDE * (SCREEN_HIGH + 1), 0, SCREEN_WIDE);
|
|
|
|
if (_audioStream) {
|
|
_vm->_mixer->stopHandle(_audioHandle);
|
|
|
|
delete _audioStream;
|
|
_audioStream = 0;
|
|
}
|
|
|
|
// Set the screen beginning to the second line (ie. past the off-screen line)
|
|
ScreenBeg = memoryBuffer + SCREEN_WIDTH;
|
|
Au_Prev1 = Au_Prev2 = 0;
|
|
}
|
|
|
|
void BMVPlayer::PrepAudio(const byte *sourceData, int blobCount, byte *destPtr) {
|
|
uint16 dx1 = Au_Prev1;
|
|
uint16 dx2 = Au_Prev2;
|
|
|
|
uint16 *destP = (uint16 *)destPtr;
|
|
const int8 *srcP = (const int8 *)sourceData;
|
|
|
|
// Blob Loop
|
|
while (blobCount-- > 0) {
|
|
uint32 ebx = (uint8) *srcP++;
|
|
uint32 ebp = ebx & 0x1E;
|
|
|
|
int blobSize = SAM_P_BLOB / 2;
|
|
|
|
ebx = (((ebx & 0x0F) << 4) | ((ebx & 0xF0) >> 4)) & 0x1E;
|
|
|
|
ebp = Au_DecTable[ebp >> 1];
|
|
ebx = Au_DecTable[ebx >> 1];
|
|
|
|
// Inner loop
|
|
while (blobSize-- > 0) {
|
|
uint32 s1 = (((int32) *srcP++) * ((int32) ebp)) >> 5;
|
|
uint32 s2 = (((int32) *srcP++) * ((int32) ebx)) >> 5;
|
|
|
|
dx1 += s1 & 0xFFFF;
|
|
dx2 += s2 & 0xFFFF;
|
|
|
|
*destP++ = TO_BE_16(dx1);
|
|
*destP++ = TO_BE_16(dx2);
|
|
}
|
|
}
|
|
|
|
Au_Prev1 = dx1;
|
|
Au_Prev2 = dx2;
|
|
}
|
|
|
|
//----------------- BMV FUNCTIONS ----------------------------
|
|
|
|
BMVPlayer::BMVPlayer() {
|
|
bOldAudio = 0;
|
|
bMovieOn = 0;
|
|
bAbort = 0;
|
|
bmvEscape = 0;
|
|
|
|
memset(szMovieFile, 0, sizeof(szMovieFile));
|
|
|
|
bigBuffer = 0;
|
|
nextUseOffset = 0;
|
|
nextSoundOffset = 0;
|
|
wrapUseOffset = 0;
|
|
mostFutureOffset = 0;
|
|
currentFrame = 0;
|
|
currentSoundFrame = 0;
|
|
numAdvancePackets = 0;
|
|
nextReadSlot = 0;
|
|
bFileEnd = 0;
|
|
|
|
memset(moviePal, 0, sizeof(moviePal));
|
|
|
|
blobsInBuffer = 0;
|
|
|
|
memset(texts, 0, sizeof(texts));
|
|
|
|
talkColour = 0;
|
|
bigProblemCount = 0;
|
|
bIsText = 0;
|
|
movieTick = 0;
|
|
startTick = 0;
|
|
nextMovieTime = 0;
|
|
Au_Prev1 = 0;
|
|
Au_Prev2 = 0;
|
|
ScreenBeg = 0;
|
|
screenBuffer = 0;
|
|
audioStarted = 0;
|
|
_audioStream = 0;
|
|
nextMaintain = 0;
|
|
}
|
|
|
|
/**
|
|
* Called when a packet contains a palette field.
|
|
* Build a COLORREF array and queue it to the DAC.
|
|
*/
|
|
void BMVPlayer::MoviePalette(int paletteOffset) {
|
|
int i;
|
|
byte *r;
|
|
|
|
r = bigBuffer + paletteOffset;
|
|
|
|
for (i = 0; i < 256; i++, r += 3) {
|
|
moviePal[i] = TINSEL_RGB(*r, *(r + 1), *(r + 2));
|
|
}
|
|
|
|
UpdateDACqueue(1, 255, &moviePal[1]);
|
|
|
|
// Don't clobber talk
|
|
if (talkColour != 0)
|
|
SetTextPal(talkColour);
|
|
}
|
|
|
|
void BMVPlayer::InitialiseMovieSound() {
|
|
_audioStream = Audio::makeQueuingAudioStream(22050, true);
|
|
audioStarted = false;
|
|
}
|
|
|
|
void BMVPlayer::StartMovieSound() {
|
|
}
|
|
|
|
void BMVPlayer::FinishMovieSound() {
|
|
if (_audioStream) {
|
|
_vm->_mixer->stopHandle(_audioHandle);
|
|
|
|
delete _audioStream;
|
|
_audioStream = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when a packet contains an audio field.
|
|
*/
|
|
void BMVPlayer::MovieAudio(int audioOffset, int blobs) {
|
|
if (audioOffset == 0 && blobs == 0)
|
|
blobs = 57;
|
|
|
|
byte *data = (byte *)malloc(blobs * 128);
|
|
|
|
if (audioOffset != 0)
|
|
PrepAudio(bigBuffer+audioOffset, blobs, data);
|
|
else
|
|
memset(data, 0, blobs * 128);
|
|
|
|
_audioStream->queueBuffer(data, blobs * 128, DisposeAfterUse::YES, Audio::FLAG_16BITS | Audio::FLAG_STEREO);
|
|
|
|
if (currentSoundFrame == ADVANCE_SOUND) {
|
|
if (!audioStarted) {
|
|
_vm->_mixer->playStream(Audio::Mixer::kSFXSoundType,
|
|
&_audioHandle, _audioStream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
|
|
audioStarted = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*-----------------------------------------------------*\
|
|
|-------------------------------------------------------|
|
|
\*-----------------------------------------------------*/
|
|
|
|
void BMVPlayer::FettleMovieText() {
|
|
int i;
|
|
|
|
bIsText = false;
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
if (texts[i].pText) {
|
|
if (currentFrame > texts[i].dieFrame) {
|
|
MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), texts[i].pText);
|
|
texts[i].pText = NULL;
|
|
} else {
|
|
MultiForceRedraw(texts[i].pText);
|
|
bIsText = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*-----------------------------------------------------*\
|
|
|-------------------------------------------------------|
|
|
\*-----------------------------------------------------*/
|
|
|
|
void BMVPlayer::BmvDrawText(bool bDraw) {
|
|
int w, h, x, y;
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
if (texts[i].pText) {
|
|
x = MultiLeftmost(texts[i].pText);
|
|
y = MultiHighest(texts[i].pText);
|
|
w = MIN(MultiRightmost(texts[i].pText) + 1, (int)SCREEN_WIDTH) - x;
|
|
h = MIN(MultiLowest(texts[i].pText) + 1, SCREEN_HIGH) - y;
|
|
|
|
const byte *src = ScreenBeg + (y * SCREEN_WIDTH) + x;
|
|
byte *dest = (byte *)_vm->screen().getBasePtr(x, y);
|
|
|
|
for (int j = 0; j < h; j++, dest += SCREEN_WIDTH, src += SCREEN_WIDTH) {
|
|
memcpy(dest, src, w);
|
|
}
|
|
|
|
if (bDraw) {
|
|
Common::Point ptWin;
|
|
Common::Rect rcPlayClip;
|
|
|
|
ptWin.x = ptWin.y = 0;
|
|
rcPlayClip.left = x;
|
|
rcPlayClip.top = y;
|
|
rcPlayClip.right = x+w;
|
|
rcPlayClip.bottom = y+h;
|
|
UpdateClipRect(GetPlayfieldList(FIELD_STATUS), &ptWin, &rcPlayClip);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*-----------------------------------------------------*\
|
|
|-------------------------------------------------------|
|
|
\*-----------------------------------------------------*/
|
|
|
|
void BMVPlayer::MovieText(CORO_PARAM, int stringId, int x, int y, int fontId, COLORREF *pTalkColour, int duration) {
|
|
SCNHANDLE hFont;
|
|
int index;
|
|
|
|
if (fontId == 1) {
|
|
// It's a 'print'
|
|
|
|
hFont = GetTagFontHandle();
|
|
index = 0;
|
|
} else {
|
|
// It's a 'talk'
|
|
|
|
if (pTalkColour != NULL)
|
|
SetTextPal(*pTalkColour);
|
|
hFont = GetTalkFontHandle();
|
|
index = 1;
|
|
}
|
|
|
|
if (texts[index].pText)
|
|
MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), texts[index].pText);
|
|
|
|
LoadSubString(stringId, 0, TextBufferAddr(), TBUFSZ);
|
|
|
|
texts[index].dieFrame = currentFrame + duration;
|
|
texts[index].pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS),
|
|
TextBufferAddr(),
|
|
0,
|
|
x, y,
|
|
hFont,
|
|
TXT_CENTRE, 0);
|
|
KeepOnScreen(texts[index].pText, &x, &y);
|
|
}
|
|
|
|
/**
|
|
* Called when a packet contains a command field.
|
|
*/
|
|
int BMVPlayer::MovieCommand(char cmd, int commandOffset) {
|
|
if (cmd & CD_PRINT) {
|
|
PRINT_CMD *pCmd = (PRINT_CMD *)(bigBuffer + commandOffset);
|
|
|
|
MovieText(nullContext, (int16)READ_LE_UINT16(&pCmd->stringId),
|
|
(int16)READ_LE_UINT16(&pCmd->x),
|
|
(int16)READ_LE_UINT16(&pCmd->y),
|
|
pCmd->fontId,
|
|
NULL,
|
|
pCmd->duration);
|
|
|
|
return sz_CMD_PRINT_pkt;
|
|
} else {
|
|
if (_vm->_config->_useSubtitles) {
|
|
TALK_CMD *pCmd = (TALK_CMD *)(bigBuffer + commandOffset);
|
|
talkColour = TINSEL_RGB(pCmd->r, pCmd->g, pCmd->b);
|
|
|
|
MovieText(nullContext, (int16)READ_LE_UINT16(&pCmd->stringId),
|
|
(int16)READ_LE_UINT16(&pCmd->x),
|
|
(int16)READ_LE_UINT16(&pCmd->y),
|
|
0,
|
|
&talkColour,
|
|
pCmd->duration);
|
|
}
|
|
return sz_CMD_TALK_pkt;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called from PlayMovie() in tinlib.cpp
|
|
* Kicks off the playback of a movie, and waits around
|
|
* until it's finished.
|
|
*/
|
|
void BMVPlayer::PlayBMV(CORO_PARAM, SCNHANDLE hFileStem, int myEscape) {
|
|
CORO_BEGIN_CONTEXT;
|
|
CORO_END_CONTEXT(_ctx);
|
|
|
|
CORO_BEGIN_CODE(_ctx);
|
|
|
|
assert(!bMovieOn);
|
|
|
|
strcpy(szMovieFile, (char *)LockMem(hFileStem));
|
|
strcat(szMovieFile, BMOVIE_EXTENSION);
|
|
|
|
assert(strlen(szMovieFile) <= 12);
|
|
|
|
bMovieOn = true;
|
|
bAbort = false;
|
|
bmvEscape = myEscape;
|
|
|
|
do {
|
|
CORO_SLEEP(1);
|
|
} while (bMovieOn);
|
|
|
|
CORO_END_CODE;
|
|
}
|
|
|
|
/**
|
|
* Given a packet offset, calculates the offset of the
|
|
* next packet. The packet may not yet exist, and the
|
|
*return value may be off the end of bigBuffer.
|
|
*/
|
|
int BMVPlayer::FollowingPacket(int thisPacket, bool bReallyImportant) {
|
|
unsigned char *data;
|
|
int nextSlot, length;
|
|
|
|
// Set pointer to thisPacket's data
|
|
data = bigBuffer + thisPacket;
|
|
|
|
switch (*data) {
|
|
case CD_SLOT_NOP:
|
|
nextSlot = thisPacket/SLOT_SIZE;
|
|
if (thisPacket%SLOT_SIZE)
|
|
nextSlot++;
|
|
|
|
return nextSlot * SLOT_SIZE;
|
|
|
|
case CD_LE_FIN:
|
|
return -1;
|
|
|
|
default:
|
|
// Following 3 bytes are the length
|
|
if (bReallyImportant) {
|
|
// wrapped round or at least 3 bytes
|
|
assert(((nextReadSlot * SLOT_SIZE) < thisPacket) ||
|
|
((thisPacket + 3) < (nextReadSlot * SLOT_SIZE)));
|
|
|
|
if ((nextReadSlot * SLOT_SIZE >= thisPacket) &&
|
|
((thisPacket + 3) >= nextReadSlot*SLOT_SIZE)) {
|
|
// MaintainBuffer calls this back, but with false
|
|
MaintainBuffer();
|
|
}
|
|
} else {
|
|
// not wrapped and not 3 bytes
|
|
if (nextReadSlot*SLOT_SIZE >= thisPacket && thisPacket+3 >= nextReadSlot*SLOT_SIZE)
|
|
return thisPacket + 3;
|
|
}
|
|
length = (int32)READ_LE_UINT32(bigBuffer + thisPacket + 1);
|
|
length &= 0x00ffffff;
|
|
return thisPacket + length + 4;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called from the foreground when starting playback of a movie.
|
|
*/
|
|
void BMVPlayer::LoadSlots(int number) {
|
|
int nextOffset;
|
|
|
|
assert(number + nextReadSlot < NUM_SLOTS);
|
|
|
|
if (stream.read(bigBuffer + nextReadSlot*SLOT_SIZE, number * SLOT_SIZE) !=
|
|
(uint32)(number * SLOT_SIZE)) {
|
|
int possibleSlots;
|
|
|
|
// May be a short file
|
|
possibleSlots = stream.size() / SLOT_SIZE;
|
|
if ((number + nextReadSlot) > possibleSlots) {
|
|
bFileEnd = true;
|
|
nextReadSlot = possibleSlots;
|
|
} else
|
|
error(FILE_IS_CORRUPT, szMovieFile);
|
|
}
|
|
|
|
nextReadSlot += number;
|
|
|
|
nextOffset = FollowingPacket(nextUseOffset, true);
|
|
while (nextOffset < nextReadSlot*SLOT_SIZE
|
|
&& nextOffset != -1) {
|
|
numAdvancePackets++;
|
|
mostFutureOffset = nextOffset;
|
|
nextOffset = FollowingPacket(mostFutureOffset, false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called from the foreground when starting playback of a movie.
|
|
*/
|
|
void BMVPlayer::InitialiseBMV() {
|
|
if (!stream.open(szMovieFile))
|
|
error(CANNOT_FIND_FILE, szMovieFile);
|
|
|
|
// Grab the data buffer
|
|
bigBuffer = (byte *)malloc(NUM_SLOTS * SLOT_SIZE);
|
|
if (bigBuffer == NULL)
|
|
error(NO_MEM, "FMV data buffer");
|
|
|
|
// Screen buffer (2 lines more than screen
|
|
screenBuffer = (byte *)malloc(SCREEN_WIDTH * (SCREEN_HIGH + 2));
|
|
if (screenBuffer == NULL)
|
|
error(NO_MEM, "FMV screen buffer");
|
|
|
|
// Pass the sceen buffer to the decompresser
|
|
InitBMV(screenBuffer);
|
|
|
|
// Initialise some stuff
|
|
nextUseOffset = 0;
|
|
nextSoundOffset = 0;
|
|
wrapUseOffset = -1;
|
|
mostFutureOffset = 0;
|
|
currentFrame = 0;
|
|
currentSoundFrame = 0;
|
|
numAdvancePackets = 0;
|
|
nextReadSlot = 0;
|
|
bFileEnd = false;
|
|
blobsInBuffer = 0;
|
|
memset(texts, 0, sizeof(texts));
|
|
talkColour = 0;
|
|
bigProblemCount = 0;
|
|
|
|
movieTick = 0;
|
|
|
|
bIsText = false;
|
|
|
|
// Prefetch data
|
|
LoadSlots(PREFETCH);
|
|
|
|
while (numAdvancePackets < ADVANCE_SOUND)
|
|
LoadSlots(1);
|
|
|
|
// Initialise the sound channel
|
|
InitialiseMovieSound();
|
|
}
|
|
|
|
/**
|
|
* Called from the foreground when ending playback of a movie.
|
|
*/
|
|
void BMVPlayer::FinishBMV() {
|
|
int i;
|
|
|
|
// Notify the sound channel
|
|
FinishMovieSound();
|
|
|
|
// Close the file stream
|
|
if (stream.isOpen())
|
|
stream.close();
|
|
|
|
// Release the data buffer
|
|
free(bigBuffer);
|
|
bigBuffer = NULL;
|
|
|
|
// Release the screen buffer
|
|
free(screenBuffer);
|
|
screenBuffer = NULL;
|
|
|
|
// Ditch any text objects
|
|
for (i = 0; i < 2; i++) {
|
|
if (texts[i].pText) {
|
|
MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), texts[i].pText);
|
|
texts[i].pText = NULL;
|
|
}
|
|
}
|
|
bMovieOn = false;
|
|
|
|
nextMovieTime = 0;
|
|
|
|
// Test for 'twixt-movie glitch
|
|
ClearScreen();
|
|
}
|
|
|
|
/**
|
|
* MaintainBuffer()
|
|
*/
|
|
bool BMVPlayer::MaintainBuffer() {
|
|
int nextOffset;
|
|
|
|
// No action if the file is all read
|
|
if (bFileEnd == true)
|
|
return false;
|
|
|
|
// See if next complete packet exists
|
|
// and if so, if it will fit in the top of the buffer
|
|
nextOffset = FollowingPacket(mostFutureOffset, false);
|
|
if (nextOffset == -1) {
|
|
// No following packets
|
|
return false;
|
|
} else if (nextOffset > NUM_SLOTS * SLOT_SIZE) {
|
|
// The current unfinished packet will not fit
|
|
// Copy this slot to slot 0
|
|
|
|
// Not if we're still using it!!!
|
|
// Or, indeed, if the player is still lagging
|
|
if (nextUseOffset < SLOT_SIZE || nextUseOffset > mostFutureOffset) {
|
|
// Slot 0 is still in use, buffer is full!
|
|
return false;
|
|
}
|
|
|
|
// Tell data player where to make the jump
|
|
wrapUseOffset = mostFutureOffset;
|
|
|
|
// mostFuture Offset is now in slot 0
|
|
mostFutureOffset %= SLOT_SIZE;
|
|
|
|
// Copy the data we already have for unfinished packet
|
|
memcpy(bigBuffer + mostFutureOffset,
|
|
bigBuffer + wrapUseOffset,
|
|
SLOT_SIZE - mostFutureOffset);
|
|
|
|
// Next read is into slot 1
|
|
nextReadSlot = 1;
|
|
}
|
|
|
|
if (nextReadSlot == NUM_SLOTS) {
|
|
// Want to go to slot zero, wait if still in use
|
|
if (nextUseOffset < SLOT_SIZE) {
|
|
// Slot 0 is still in use, buffer is full!
|
|
return false;
|
|
}
|
|
|
|
// nextOffset must be the buffer size
|
|
assert(nextOffset == NUM_SLOTS*SLOT_SIZE);
|
|
|
|
// wrapUseOffset must not be set
|
|
assert(wrapUseOffset == -1);
|
|
wrapUseOffset = nextOffset;
|
|
|
|
nextReadSlot = 0;
|
|
mostFutureOffset = 0;
|
|
}
|
|
|
|
// Don't overwrite unused data
|
|
if (nextUseOffset / SLOT_SIZE == nextReadSlot) {
|
|
// Buffer is full!
|
|
return false;
|
|
}
|
|
|
|
if (stream.read(bigBuffer + nextReadSlot * SLOT_SIZE, SLOT_SIZE) != SLOT_SIZE) {
|
|
bFileEnd = true;
|
|
}
|
|
|
|
// Read next slot next time
|
|
nextReadSlot++;
|
|
|
|
// Find new mostFutureOffset
|
|
nextOffset = FollowingPacket(mostFutureOffset, false);
|
|
while (nextOffset < nextReadSlot*SLOT_SIZE
|
|
&& nextOffset != -1) {
|
|
numAdvancePackets++;
|
|
mostFutureOffset = nextOffset;
|
|
nextOffset = FollowingPacket(mostFutureOffset, false);
|
|
}
|
|
|
|
// New test feature for e.g. short files
|
|
if (bFileEnd && *(bigBuffer+mostFutureOffset) != CD_LE_FIN)
|
|
bAbort = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* DoBMVFrame
|
|
*/
|
|
bool BMVPlayer::DoBMVFrame() {
|
|
unsigned char *data;
|
|
int graphOffset, length;
|
|
signed short xscr;
|
|
|
|
if (nextUseOffset == wrapUseOffset) {
|
|
nextUseOffset %= SLOT_SIZE;
|
|
}
|
|
|
|
while (nextUseOffset == mostFutureOffset) {
|
|
data = bigBuffer + nextUseOffset;
|
|
if (*data != CD_LE_FIN) {
|
|
// Don't get stuck in an infinite loop
|
|
if (!MaintainBuffer()) {
|
|
FinishBMV();
|
|
return false;
|
|
}
|
|
|
|
if (nextUseOffset == wrapUseOffset) {
|
|
nextUseOffset %= SLOT_SIZE;
|
|
}
|
|
} else
|
|
break;
|
|
}
|
|
|
|
// Set pointer to data
|
|
data = bigBuffer + nextUseOffset;
|
|
|
|
// If still at most Future, it must be last
|
|
if (nextUseOffset == mostFutureOffset) {
|
|
assert(*data == CD_LE_FIN);
|
|
}
|
|
|
|
switch (*data) {
|
|
case CD_SLOT_NOP:
|
|
nextUseOffset = FollowingPacket(nextUseOffset, true);
|
|
if (nextUseOffset == wrapUseOffset) {
|
|
nextUseOffset %= SLOT_SIZE;
|
|
wrapUseOffset = -1;
|
|
}
|
|
numAdvancePackets--;
|
|
return false;
|
|
|
|
case CD_LE_FIN:
|
|
FinishBMV();
|
|
numAdvancePackets--;
|
|
return true;
|
|
|
|
default:
|
|
length = (int32)READ_LE_UINT32(data + 1);
|
|
length &= 0x00ffffff;
|
|
|
|
graphOffset = nextUseOffset + 4; // Skip command byte and length
|
|
|
|
if (*data & CD_AUDIO) {
|
|
if (bOldAudio) {
|
|
graphOffset += sz_AUDIO_pkt; // Skip audio data
|
|
length -= sz_AUDIO_pkt;
|
|
} else {
|
|
int blobs;
|
|
|
|
blobs = *(bigBuffer + graphOffset);
|
|
blobs *= SZ_C_BLOB;
|
|
graphOffset += (blobs + 1);
|
|
length -= (blobs + 1);
|
|
}
|
|
}
|
|
|
|
if (*data & CD_CMND) {
|
|
int cmdLen;
|
|
|
|
// Process command and skip data
|
|
cmdLen = MovieCommand(*data, graphOffset);
|
|
|
|
graphOffset += cmdLen;
|
|
length -= cmdLen;
|
|
}
|
|
|
|
if (*data & CD_CMAP) {
|
|
MoviePalette(graphOffset);
|
|
graphOffset += sz_CMAP_pkt; // Skip palette data
|
|
length -= sz_CMAP_pkt;
|
|
}
|
|
|
|
if (*data & CD_XSCR) {
|
|
xscr = (int16)READ_LE_UINT16(bigBuffer + graphOffset);
|
|
graphOffset += sz_XSCR_pkt; // Skip scroll offset
|
|
length -= sz_XSCR_pkt;
|
|
} else if (*data & BIT0)
|
|
xscr = -640;
|
|
else
|
|
xscr = 0;
|
|
|
|
PrepBMV(ScreenBeg, bigBuffer + graphOffset, length, xscr);
|
|
|
|
currentFrame++;
|
|
numAdvancePackets--;
|
|
|
|
nextUseOffset = FollowingPacket(nextUseOffset, true);
|
|
if (nextUseOffset == wrapUseOffset) {
|
|
nextUseOffset %= SLOT_SIZE;
|
|
wrapUseOffset = -1;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DoSoundFrame
|
|
*/
|
|
bool BMVPlayer::DoSoundFrame() {
|
|
unsigned char *data;
|
|
int graphOffset;
|
|
|
|
if (nextSoundOffset == wrapUseOffset) {
|
|
nextSoundOffset %= SLOT_SIZE;
|
|
}
|
|
|
|
// Make sure the full slot is here
|
|
while (nextSoundOffset == mostFutureOffset) {
|
|
data = bigBuffer + nextSoundOffset;
|
|
if (*data != CD_LE_FIN) {
|
|
// Don't get stuck in an infinite loop
|
|
if (!MaintainBuffer()) {
|
|
if (!bOldAudio)
|
|
MovieAudio(0, 0);
|
|
currentSoundFrame++;
|
|
return false;
|
|
}
|
|
|
|
if (nextSoundOffset == wrapUseOffset) {
|
|
nextSoundOffset %= SLOT_SIZE;
|
|
}
|
|
} else
|
|
break;
|
|
}
|
|
|
|
// Set pointer to data
|
|
data = bigBuffer + nextSoundOffset;
|
|
|
|
// If still at most Future, it must be last
|
|
if (nextSoundOffset == mostFutureOffset) {
|
|
assert(*data == CD_LE_FIN);
|
|
}
|
|
|
|
switch (*data) {
|
|
case CD_SLOT_NOP:
|
|
nextSoundOffset = FollowingPacket(nextSoundOffset, true);
|
|
if (nextSoundOffset == wrapUseOffset) {
|
|
nextSoundOffset %= SLOT_SIZE;
|
|
}
|
|
return false;
|
|
|
|
case CD_LE_FIN:
|
|
if (!bOldAudio)
|
|
MovieAudio(0, 0);
|
|
currentSoundFrame++;
|
|
return true;
|
|
|
|
default:
|
|
if (*data & CD_AUDIO) {
|
|
graphOffset = nextSoundOffset + 4; // Skip command byte and length
|
|
|
|
if (!bOldAudio) {
|
|
int blobs = *(bigBuffer + graphOffset);
|
|
MovieAudio(graphOffset+1, blobs);
|
|
}
|
|
} else {
|
|
if (!bOldAudio)
|
|
MovieAudio(0, 0);
|
|
}
|
|
|
|
nextSoundOffset = FollowingPacket(nextSoundOffset, false);
|
|
if (nextSoundOffset == wrapUseOffset) {
|
|
nextSoundOffset %= SLOT_SIZE;
|
|
}
|
|
currentSoundFrame++;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* CopyMovieToScreen
|
|
*/
|
|
void BMVPlayer::CopyMovieToScreen() {
|
|
// Not if not up and running yet!
|
|
if (!screenBuffer || (currentFrame == 0)) {
|
|
ForceEntireRedraw();
|
|
DrawBackgnd();
|
|
return;
|
|
}
|
|
|
|
// The movie surface is slightly less high than the output screen (429 rows versus 432).
|
|
// Because of this, there's some extra line clearing above and below the displayed area
|
|
int yStart = (SCREEN_HEIGHT - SCREEN_HIGH) / 2;
|
|
memset(_vm->screen().getBasePtr(0, 0), 0, yStart * SCREEN_WIDTH);
|
|
memcpy(_vm->screen().getBasePtr(0, yStart), ScreenBeg, SCREEN_WIDTH * SCREEN_HIGH);
|
|
memset(_vm->screen().getBasePtr(0, yStart + SCREEN_HIGH), 0,
|
|
(SCREEN_HEIGHT - SCREEN_HIGH - yStart) * SCREEN_WIDTH);
|
|
|
|
BmvDrawText(true);
|
|
PalettesToVideoDAC(); // Keep palette up-to-date
|
|
UpdateScreenRect(Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
|
|
g_system->updateScreen();
|
|
BmvDrawText(false);
|
|
}
|
|
|
|
/**
|
|
* LookAtBuffers
|
|
*/
|
|
void BMVPlayer::LookAtBuffers() {
|
|
// FIXME: What's the point of this function???
|
|
// Maybe to ensure the relevant data is loaded into cache by the CPU?
|
|
static int junk; // FIXME: Avoid non-const global vars
|
|
int i;
|
|
|
|
if (bigBuffer) {
|
|
for (i = 0; i < NUM_SLOTS; i++)
|
|
junk += bigBuffer[i*SLOT_SIZE];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles playback of any active movie. Called from the foreground 24 times a second.
|
|
*/
|
|
void BMVPlayer::FettleBMV() {
|
|
|
|
int refFrame;
|
|
// Tick counter needs to be incrementing at a 24Hz rate
|
|
int tick = movieTick++;
|
|
|
|
if (!bMovieOn)
|
|
return;
|
|
|
|
// Escape the rest if appropriate
|
|
if (bAbort || (bmvEscape && bmvEscape != GetEscEvents())) {
|
|
FinishBMV();
|
|
return;
|
|
}
|
|
|
|
LookAtBuffers();
|
|
|
|
if (!stream.isOpen()) {
|
|
int i;
|
|
|
|
// First time in with this movie
|
|
|
|
InitialiseBMV();
|
|
|
|
for (i = 0; i < ADVANCE_SOUND;) {
|
|
if (DoSoundFrame())
|
|
i++;
|
|
}
|
|
startTick = -ONE_SECOND / 4; // 1/4 second
|
|
return;
|
|
}
|
|
|
|
if (startTick < 0) {
|
|
startTick++;
|
|
return;
|
|
}
|
|
if (startTick == 0) {
|
|
startTick = tick;
|
|
nextMaintain = startTick + 1;
|
|
StartMovieSound();
|
|
}
|
|
|
|
nextMovieTime = g_system->getMillis() + 41;
|
|
|
|
FettleMovieText();
|
|
|
|
if (bigProblemCount < PT_A) {
|
|
refFrame = currentSoundFrame;
|
|
|
|
while (currentSoundFrame < ((tick+1-startTick)/2 + ADVANCE_SOUND) && bMovieOn) {
|
|
if (currentSoundFrame == refFrame+PT_B)
|
|
break;
|
|
|
|
DoSoundFrame();
|
|
}
|
|
}
|
|
|
|
// Time to process a frame (or maybe more)
|
|
if (bigProblemCount < PT_A) {
|
|
refFrame = currentFrame;
|
|
|
|
while ((currentFrame < (tick-startTick)/2) && bMovieOn) {
|
|
DoBMVFrame();
|
|
|
|
if (currentFrame == refFrame+PT_B) {
|
|
bigProblemCount++;
|
|
|
|
if (bigProblemCount == PT_A) {
|
|
startTick = tick-(2*currentFrame);
|
|
bigProblemCount = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (currentFrame == refFrame || currentFrame <= refFrame+3) {
|
|
bigProblemCount = 0;
|
|
}
|
|
} else {
|
|
while (currentFrame < (tick-startTick)/2 && bMovieOn) {
|
|
DoBMVFrame();
|
|
}
|
|
}
|
|
|
|
if (tick >= nextMaintain || numAdvancePackets < SUBSEQUENT_SOUND) {
|
|
MaintainBuffer();
|
|
nextMaintain = tick + 2;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if a movie is playing.
|
|
*/
|
|
bool BMVPlayer::MoviePlaying() {
|
|
return bMovieOn;
|
|
}
|
|
|
|
/**
|
|
* Returns the audio lag in ms
|
|
*/
|
|
int32 BMVPlayer::MovieAudioLag() {
|
|
if (!bMovieOn || !_audioStream)
|
|
return 0;
|
|
|
|
// Calculate lag
|
|
int32 playLength = (movieTick - startTick - 1) * ((((uint32) 1000) << 10) / 24);
|
|
return (playLength - (((int32) _vm->_mixer->getSoundElapsedTime(_audioHandle)) << 10)) >> 10;
|
|
}
|
|
|
|
uint32 BMVPlayer::NextMovieTime() {
|
|
return nextMovieTime;
|
|
}
|
|
|
|
void BMVPlayer::AbortMovie() {
|
|
bAbort = true;
|
|
}
|
|
|
|
} // End of namespace Tinsel
|