1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  * The movie player.
22  */
23 
24 #include "tinsel/tinsel.h"
25 #include "tinsel/background.h"
26 #include "tinsel/bmv.h"
27 #include "tinsel/cliprect.h"
28 #include "tinsel/config.h"
29 #include "tinsel/dw.h"
30 #include "tinsel/events.h"
31 #include "tinsel/font.h"
32 #include "tinsel/graphics.h"
33 #include "tinsel/handle.h"
34 #include "tinsel/multiobj.h"
35 #include "tinsel/sched.h"
36 #include "tinsel/strres.h"
37 #include "tinsel/text.h"
38 #include "tinsel/timers.h"
39 #include "tinsel/tinlib.h"
40 #include "tinsel/tinsel.h"
41 
42 #include "audio/audiostream.h"
43 #include "audio/decoders/raw.h"
44 
45 #include "common/textconsole.h"
46 
47 namespace Tinsel {
48 
49 //----------------- LOCAL DEFINES ----------------------------
50 
51 #define BMOVIE_EXTENSION	".bmv"
52 
53 #define SZ_C_BLOB	65
54 #define SZ_U_BLOB	128
55 
56 #define BLANK_SOUND	0x0	// for 16 bit silence
57 
58 #define PT_A	20	// Number of times PT_B may be reached
59 #define PT_B	6
60 
61 
62 #define SLOT_SIZE	(25*1024)
63 //#define NUM_SLOTS	168
64 #define NUM_SLOTS	122		// -> ~ 3MB
65 
66 
67 #define PREFETCH	(NUM_SLOTS/2)	// For initial test
68 
69 #define ADVANCE_SOUND		18	// 1 1/2 seconds
70 #define SUBSEQUENT_SOUND	6	// 1/2 second
71 
72 
73 
74 // PACKET TYPE IDs & FLAGS
75 
76 #define CD_SLOT_NOP	0x00	// Skip to next slot
77 #define CD_LE_FIN	0x01	// End of movie
78 #define CD_PDELTA	0x02	// Image compressed to previous one
79 #define CD_SDELTA	0x03	// Image self-compressed
80 
81 #define BIT0		0x01
82 
83 #define CD_XSCR		0x04	// Screen has a scroll offset
84 #define CD_CMAP	0x08	// Color map is included
85 #define CD_CMND	0x10	// Command is included
86 #define CD_AUDIO	0x20	// Audio data is included
87 #define CD_EXTEND	0x40	// Extended modes "A"-"z"
88 #define CD_PRINT	0x80	// goes in conjunction with CD_CMD
89 
90 // Data field sizes
91 #define sz_XSCR_pkt		2
92 #define sz_CMAP_pkt		0x300
93 #define sz_CMD_TALK_pkt		10
94 #define sz_CMD_PRINT_pkt	8
95 #define sz_AUDIO_pkt		3675
96 
97 
98 struct TALK_CMD {
99 	short	x;
100 	short	y;
101 	short	stringId;
102 	unsigned char	duration;
103 	char	r;			// may be b!
104 	char	g;
105 	char	b;			// may be r!
106 };
107 
108 struct PRINT_CMD {
109 	int16	x;
110 	int16	y;
111 	int16	stringId;
112 	unsigned char	duration;
113 	unsigned char	fontId;
114 };
115 
116 
117 //----------------- LOCAL GLOBAL DATA ------------------------
118 
119 static const uint16 Au_DecTable[16] = {16512, 8256, 4128, 2064, 1032, 516, 258, 192,
120 		129, 88, 64, 56, 48, 40, 36, 32};
121 
122 //---------------- DECOMPRESSOR FUNCTIONS --------------------
123 
124 #define SCREEN_WIDE 640
125 #define SCREEN_HIGH 429
126 #define SAM_P_BLOB (32 * 2)
127 
128 #define ROR(x,v) x = ((x >> (v%32)) | (x << (32 - (v%32))))
129 #define ROL(x,v) x = ((x << (v%32)) | (x >> (32 - (v%32))))
130 #define NEXT_BYTE(v) v = (forwardDirection ? v + 1 : v - 1)
131 
PrepBMV(byte * ScreenBeg,const byte * sourceData,int length,short deltaFetchDisp)132 static void PrepBMV(byte *ScreenBeg, const byte *sourceData, int length, short deltaFetchDisp) {
133 	uint8 NibbleHi = 0;
134 	uint32 edx = length;
135 	int32 ebx = deltaFetchDisp;
136 	const byte *src;
137 	byte *dst, *endDst;
138 
139 	const bool forwardDirection = (deltaFetchDisp <= -SCREEN_WIDE) || (deltaFetchDisp >= 0);
140 	if (forwardDirection) {
141 		// Forward decompression
142 		src = sourceData;
143 		dst = ScreenBeg;
144 		endDst = ScreenBeg + SCREEN_WIDE * SCREEN_HIGH;
145 	} else {
146 		src = sourceData + length - 1;
147 		dst = ScreenBeg + SCREEN_WIDE * SCREEN_HIGH - 1;
148 		endDst = ScreenBeg - 1;
149 	}
150 
151 	bool firstLoop, flag;
152 
153 	int loopCtr = 0;
154 	do {
155 		uint32 eax = 0;
156 		uint32 bitshift = 0;
157 		flag = false;
158 
159 		if ((loopCtr == 0) || (edx == 4)) {
160 			// Get the next hi,lo nibble
161 			eax = (eax & 0xffffff00) | *src;
162 			firstLoop = true;
163 		} else {
164 			// Get the high nibble
165 			eax = (eax & 0xffffff00) | NibbleHi;
166 			firstLoop = false;
167 		}
168 
169 		// Is lo nibble '00xx'?
170 		if ((eax & 0xC) == 0) {
171 			for (;;) {
172 //@_rDN_Lp_1:
173 				// Only execute this bit first the first time into the loop
174 				if (!firstLoop) {
175 					ROR(eax, 2);
176 					bitshift += 2;
177 					eax = (eax & 0xffffff00) | *src;
178 
179 					if ((eax & 0xC) != 0)
180 						break;
181 				}
182 				firstLoop = false;
183 
184 //@_rD2nd_1:
185 				ROR(eax, 2);		// Save bi-bit into hi 2 bits
186 				bitshift += 2;			//   and increase bit-shifter
187 				// Shift another 2 bits to get hi nibble
188 				eax = (eax & 0xffffff00) | ((eax & 0xff) >> 2);
189 				NEXT_BYTE(src);
190 
191 				if ((eax & 0xC) != 0) {
192 					flag = true;
193 					ROL(eax, bitshift);
194 					break;
195 				}
196 			}
197 		} else if (loopCtr != 0) {
198 			flag = edx != 4;
199 		}
200 
201 		if (flag) {
202 //@_rdNum__1:
203 			edx = 4;			// offset rDNum_Lo ; Next nibble is a 'lo'
204 		} else {
205 // @_rDNum_1
206 			NibbleHi = ((uint8)eax) >> 4;
207 			edx = 0;			// offset rDNum_Hi ; Next nibble is a 'hi' (reserved)
208 			eax &= 0xffffff0f;
209 			NEXT_BYTE(src);
210 			ROL(eax, bitshift);
211 		}
212 //rDN_1:
213 //@_rD_or_R:
214 		bool actionFlag = (eax & 1) != 0;
215 		eax >>= 1;
216 		int byteLen = eax - 1;
217 
218 		// Move to next loop index
219 		++loopCtr;
220 		if (loopCtr == 4)
221 			loopCtr = 1;
222 
223 		if (actionFlag) {
224 			// Adjust loopCtr to fall into the correct processing case
225 			loopCtr = loopCtr % 3 + 1;
226 		}
227 
228 		switch (loopCtr) {
229 		case 1: {
230 			// @_rDelta:
231 			const byte *saved_src = src;			// Save the source pointer
232 			src = dst + ebx;			// Point it to existing data
233 
234 			while (byteLen > 0) {
235 				*dst = *src;
236 				NEXT_BYTE(src);
237 				NEXT_BYTE(dst);
238 				--byteLen;
239 			}
240 
241 			src = saved_src;
242 			break;
243 			}
244 
245 		case 2:
246 			// @_rRaw
247 			// Copy data from source to dest
248 			while (byteLen > 0) {
249 				*dst = *src;
250 				NEXT_BYTE(src);
251 				NEXT_BYTE(dst);
252 				--byteLen;
253 			}
254 			break;
255 
256 		case 3:
257 			// @_rRun
258 			// Repeating run of data
259 			eax = forwardDirection ? *(dst - 1) : *(dst + 1);
260 
261 			while (byteLen > 0) {
262 				*dst = (uint8)eax;
263 				NEXT_BYTE(dst);
264 				--byteLen;
265 			}
266 			break;
267 		default:
268 			break;
269 		}
270 	} while (dst != endDst);
271 }
272 
InitBMV(byte * memoryBuffer)273 void BMVPlayer::InitBMV(byte *memoryBuffer) {
274 	// Clear the two extra 'off-screen' rows
275 	memset(memoryBuffer, 0, SCREEN_WIDE);
276 	memset(memoryBuffer + SCREEN_WIDE * (SCREEN_HIGH + 1), 0, SCREEN_WIDE);
277 
278 	if (_audioStream) {
279 		_vm->_mixer->stopHandle(_audioHandle);
280 
281 		delete _audioStream;
282 		_audioStream = 0;
283 	}
284 
285 	// Set the screen beginning to the second line (ie. past the off-screen line)
286 	ScreenBeg = memoryBuffer + SCREEN_WIDTH;
287 	Au_Prev1 = Au_Prev2 = 0;
288 }
289 
PrepAudio(const byte * sourceData,int blobCount,byte * destPtr)290 void BMVPlayer::PrepAudio(const byte *sourceData, int blobCount, byte *destPtr) {
291 	uint16 dx1 = Au_Prev1;
292 	uint16 dx2 = Au_Prev2;
293 
294 	uint16 *destP = (uint16 *)destPtr;
295 	const int8 *srcP = (const int8 *)sourceData;
296 
297 	// Blob Loop
298 	while (blobCount-- > 0) {
299 		uint32 ebx = (uint8) *srcP++;
300 		uint32 ebp = ebx & 0x1E;
301 
302 		int blobSize = SAM_P_BLOB / 2;
303 
304 		ebx = (((ebx & 0x0F) << 4) | ((ebx & 0xF0) >> 4)) & 0x1E;
305 
306 		ebp = Au_DecTable[ebp >> 1];
307 		ebx = Au_DecTable[ebx >> 1];
308 
309 		// Inner loop
310 		while (blobSize-- > 0) {
311 			uint32 s1 = (((int32) *srcP++) * ((int32) ebp)) >> 5;
312 			uint32 s2 = (((int32) *srcP++) * ((int32) ebx)) >> 5;
313 
314 			dx1 += s1 & 0xFFFF;
315 			dx2 += s2 & 0xFFFF;
316 
317 			*destP++ = TO_BE_16(dx1);
318 			*destP++ = TO_BE_16(dx2);
319 		}
320 	}
321 
322 	Au_Prev1 = dx1;
323 	Au_Prev2 = dx2;
324 }
325 
326 //----------------- BMV FUNCTIONS ----------------------------
327 
BMVPlayer()328 BMVPlayer::BMVPlayer() {
329 	bOldAudio = 0;
330 	bMovieOn = 0;
331 	bAbort = 0;
332 	bmvEscape = 0;
333 
334 	memset(szMovieFile, 0, sizeof(szMovieFile));
335 
336 	bigBuffer = 0;
337 	nextUseOffset = 0;
338 	nextSoundOffset = 0;
339 	wrapUseOffset = 0;
340 	mostFutureOffset = 0;
341 	currentFrame = 0;
342 	currentSoundFrame = 0;
343 	numAdvancePackets = 0;
344 	nextReadSlot = 0;
345 	bFileEnd = 0;
346 
347 	memset(moviePal, 0, sizeof(moviePal));
348 
349 	blobsInBuffer = 0;
350 
351 	memset(texts, 0, sizeof(texts));
352 
353 	talkColor = 0;
354 	bigProblemCount = 0;
355 	bIsText = 0;
356 	movieTick = 0;
357 	startTick = 0;
358 	nextMovieTime = 0;
359 	Au_Prev1 = 0;
360 	Au_Prev2 = 0;
361 	ScreenBeg = 0;
362 	screenBuffer = 0;
363 	audioStarted = 0;
364 	_audioStream = 0;
365 	nextMaintain = 0;
366 }
367 
368 /**
369  * Called when a packet contains a palette field.
370  * Build a COLORREF array and queue it to the DAC.
371  */
MoviePalette(int paletteOffset)372 void BMVPlayer::MoviePalette(int paletteOffset) {
373 	int	i;
374 	byte *r;
375 
376 	r = bigBuffer + paletteOffset;
377 
378 	for (i = 0; i < 256; i++, r += 3)	{
379 		moviePal[i] = TINSEL_RGB(*r, *(r + 1), *(r + 2));
380 	}
381 
382 	UpdateDACqueue(1, 255, &moviePal[1]);
383 
384 	// Don't clobber talk
385 	if (talkColor != 0)
386 		SetTextPal(talkColor);
387 }
388 
InitializeMovieSound()389 void BMVPlayer::InitializeMovieSound() {
390 	_audioStream = Audio::makeQueuingAudioStream(22050, true);
391 	audioStarted = false;
392 }
393 
StartMovieSound()394 void BMVPlayer::StartMovieSound() {
395 }
396 
FinishMovieSound()397 void BMVPlayer::FinishMovieSound() {
398 	if (_audioStream) {
399 		_vm->_mixer->stopHandle(_audioHandle);
400 
401 		delete _audioStream;
402 		_audioStream = 0;
403 	}
404 }
405 
406 /**
407  * Called when a packet contains an audio field.
408  */
MovieAudio(int audioOffset,int blobs)409 void BMVPlayer::MovieAudio(int audioOffset, int blobs) {
410 	if (audioOffset == 0 && blobs == 0)
411 		blobs = 57;
412 
413 	byte *data = (byte *)malloc(blobs * 128);
414 
415 	if (audioOffset != 0)
416 		PrepAudio(bigBuffer+audioOffset, blobs, data);
417 	else
418 		memset(data, 0, blobs * 128);
419 
420 	_audioStream->queueBuffer(data, blobs * 128, DisposeAfterUse::YES, Audio::FLAG_16BITS | Audio::FLAG_STEREO);
421 
422 	if (currentSoundFrame == ADVANCE_SOUND) {
423 		if (!audioStarted) {
424 			_vm->_mixer->playStream(Audio::Mixer::kSFXSoundType,
425 					&_audioHandle, _audioStream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
426 			audioStarted = true;
427 		}
428 	}
429 }
430 
431 /*-----------------------------------------------------*\
432 |-------------------------------------------------------|
433 \*-----------------------------------------------------*/
434 
FettleMovieText()435 void BMVPlayer::FettleMovieText() {
436 	int i;
437 
438 	bIsText = false;
439 
440 	for (i = 0; i < 2; i++) {
441 		if (texts[i].pText) {
442 			if (currentFrame > texts[i].dieFrame) {
443 				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), texts[i].pText);
444 				texts[i].pText = NULL;
445 			} else {
446 				MultiForceRedraw(texts[i].pText);
447 				bIsText = true;
448 			}
449 		}
450 	}
451 }
452 
453 /*-----------------------------------------------------*\
454 |-------------------------------------------------------|
455 \*-----------------------------------------------------*/
456 
BmvDrawText(bool bDraw)457 void BMVPlayer::BmvDrawText(bool bDraw) {
458 	int	w, h, x, y;
459 
460 	for (int i = 0; i < 2; i++) {
461 		if (texts[i].pText) {
462 			x = MultiLeftmost(texts[i].pText);
463 			y = MultiHighest(texts[i].pText);
464 			w = MIN(MultiRightmost(texts[i].pText) + 1, (int)SCREEN_WIDTH) - x;
465 			h = MIN(MultiLowest(texts[i].pText) + 1, SCREEN_HIGH) - y;
466 
467 			const byte *src = ScreenBeg + (y * SCREEN_WIDTH) + x;
468 			byte *dest = (byte *)_vm->screen().getBasePtr(x, y);
469 
470 			for (int j = 0; j < h; j++, dest += SCREEN_WIDTH, src += SCREEN_WIDTH) {
471 				memcpy(dest, src, w);
472 			}
473 
474 			if (bDraw) {
475 				Common::Point ptWin;
476 				Common::Rect rcPlayClip;
477 
478 				ptWin.x = ptWin.y = 0;
479 				rcPlayClip.left = x;
480 				rcPlayClip.top = y;
481 				rcPlayClip.right = x+w;
482 				rcPlayClip.bottom = y+h;
483 				UpdateClipRect(GetPlayfieldList(FIELD_STATUS), &ptWin,	&rcPlayClip);
484 			}
485 		}
486 	}
487 }
488 
489 /*-----------------------------------------------------*\
490 |-------------------------------------------------------|
491 \*-----------------------------------------------------*/
492 
MovieText(CORO_PARAM,int stringId,int x,int y,int fontId,COLORREF * pTalkColor,int duration)493 void BMVPlayer::MovieText(CORO_PARAM, int stringId, int x, int y, int fontId, COLORREF *pTalkColor, int duration) {
494 	SCNHANDLE hFont;
495 	int	index;
496 
497 	if (fontId == 1) {
498 		// It's a 'print'
499 
500 		hFont = GetTagFontHandle();
501 		index = 0;
502 	} else {
503 		// It's a 'talk'
504 
505 		if (pTalkColor != NULL)
506 			SetTextPal(*pTalkColor);
507 		hFont = GetTalkFontHandle();
508 		index = 1;
509 	}
510 
511 	if (texts[index].pText)
512 		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), texts[index].pText);
513 
514 	LoadSubString(stringId, 0, TextBufferAddr(), TBUFSZ);
515 
516 	texts[index].dieFrame = currentFrame + duration;
517 	texts[index].pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS),
518 						TextBufferAddr(),
519 						0,
520 						x, y,
521 						hFont,
522 						TXT_CENTER, 0);
523 	KeepOnScreen(texts[index].pText, &x, &y);
524 }
525 
526 /**
527  * Called when a packet contains a command field.
528  */
MovieCommand(char cmd,int commandOffset)529 int BMVPlayer::MovieCommand(char cmd, int commandOffset) {
530 	if (cmd & CD_PRINT) {
531 		PRINT_CMD *pCmd = (PRINT_CMD *)(bigBuffer + commandOffset);
532 
533 		MovieText(Common::nullContext, (int16)READ_16(&pCmd->stringId),
534 				(int16)READ_16(&pCmd->x),
535 				(int16)READ_16(&pCmd->y),
536 				pCmd->fontId,
537 				NULL,
538 				pCmd->duration);
539 
540 		return sz_CMD_PRINT_pkt;
541 	} else {
542 		if (_vm->_config->_useSubtitles) {
543 			TALK_CMD *pCmd = (TALK_CMD *)(bigBuffer + commandOffset);
544 			talkColor = TINSEL_RGB(pCmd->r, pCmd->g, pCmd->b);
545 
546 			MovieText(Common::nullContext, (int16)READ_16(&pCmd->stringId),
547 					(int16)READ_16(&pCmd->x),
548 					(int16)READ_16(&pCmd->y),
549 					0,
550 					&talkColor,
551 					pCmd->duration);
552 		}
553 		return sz_CMD_TALK_pkt;
554 	}
555 }
556 
557 /**
558  * Called from PlayMovie() in tinlib.cpp
559  * Kicks off the playback of a movie, and waits around
560  * until it's finished.
561  */
PlayBMV(CORO_PARAM,SCNHANDLE hFileStem,int myEscape)562 void BMVPlayer::PlayBMV(CORO_PARAM, SCNHANDLE hFileStem, int myEscape) {
563 	CORO_BEGIN_CONTEXT;
564 	CORO_END_CONTEXT(_ctx);
565 
566 	CORO_BEGIN_CODE(_ctx);
567 
568 	assert(!bMovieOn);
569 
570 	Common::strlcpy(szMovieFile, (char *)LockMem(hFileStem), 14);
571 	Common::strlcat(szMovieFile, BMOVIE_EXTENSION, 14);
572 
573 	assert(strlen(szMovieFile) <= 12);
574 
575 	bMovieOn = true;
576 	bAbort = false;
577 	bmvEscape = myEscape;
578 
579 	do {
580 		CORO_SLEEP(1);
581 	} while (bMovieOn);
582 
583 	CORO_END_CODE;
584 }
585 
586 /**
587  * Given a packet offset, calculates the offset of the
588  * next packet. The packet may not yet exist, and the
589  *return value may be off the end of bigBuffer.
590  */
FollowingPacket(int thisPacket,bool bReallyImportant)591 int BMVPlayer::FollowingPacket(int thisPacket, bool bReallyImportant) {
592 	unsigned char *data;
593 	int	nextSlot, length;
594 
595 	// Set pointer to thisPacket's data
596 	data = bigBuffer + thisPacket;
597 
598 	switch (*data) {
599 	case CD_SLOT_NOP:
600 		nextSlot = thisPacket/SLOT_SIZE;
601 		if (thisPacket%SLOT_SIZE)
602 			nextSlot++;
603 
604 		return nextSlot * SLOT_SIZE;
605 
606 	case CD_LE_FIN:
607 		return -1;
608 
609 	default:
610 		// Following 3 bytes are the length
611 		if (bReallyImportant) {
612 			// wrapped round or at least 3 bytes
613 			assert(((nextReadSlot * SLOT_SIZE) < thisPacket) ||
614 				((thisPacket + 3) < (nextReadSlot * SLOT_SIZE)));
615 
616 			if ((nextReadSlot * SLOT_SIZE >= thisPacket) &&
617 				((thisPacket + 3) >= nextReadSlot*SLOT_SIZE)) {
618 				// MaintainBuffer calls this back, but with false
619 				MaintainBuffer();
620 			}
621 		} else {
622 			// not wrapped and not 3 bytes
623 			if (nextReadSlot*SLOT_SIZE >= thisPacket && thisPacket+3 >= nextReadSlot*SLOT_SIZE)
624 				return thisPacket + 3;
625 		}
626 		length = (int32)READ_32(bigBuffer + thisPacket + 1);
627 		length &= 0x00ffffff;
628 		return thisPacket + length + 4;
629 	}
630 }
631 
632 /**
633  * Called from the foreground when starting playback of a movie.
634  */
LoadSlots(int number)635 void BMVPlayer::LoadSlots(int number) {
636 	int nextOffset;
637 
638 	assert(number + nextReadSlot < NUM_SLOTS);
639 
640 	if (stream.read(bigBuffer + nextReadSlot*SLOT_SIZE, number * SLOT_SIZE) !=
641 			(uint32)(number * SLOT_SIZE)) {
642 		int possibleSlots;
643 
644 		// May be a short file
645 		possibleSlots = stream.size() / SLOT_SIZE;
646 		if ((number + nextReadSlot) > possibleSlots) {
647 			bFileEnd = true;
648 			nextReadSlot = possibleSlots;
649 		} else
650 			error(FILE_IS_CORRUPT, szMovieFile);
651 	}
652 
653 	nextReadSlot += number;
654 
655 	nextOffset = FollowingPacket(nextUseOffset, true);
656 	while (nextOffset < nextReadSlot*SLOT_SIZE
657 			&& nextOffset != -1) {
658 		numAdvancePackets++;
659 		mostFutureOffset = nextOffset;
660 		nextOffset = FollowingPacket(mostFutureOffset, false);
661 	}
662 }
663 
664 /**
665  * Called from the foreground when starting playback of a movie.
666  */
InitializeBMV()667 void BMVPlayer::InitializeBMV() {
668 	if (!stream.open(szMovieFile))
669 		error(CANNOT_FIND_FILE, szMovieFile);
670 
671 	// Grab the data buffer
672 	bigBuffer = (byte *)malloc(NUM_SLOTS * SLOT_SIZE);
673 	if (bigBuffer == NULL)
674 		error(NO_MEM, "FMV data buffer");
675 
676 	// Screen buffer (2 lines more than screen
677 	screenBuffer = (byte *)malloc(SCREEN_WIDTH * (SCREEN_HIGH + 2));
678 	if (screenBuffer == NULL)
679 		error(NO_MEM, "FMV screen buffer");
680 
681 	// Pass the sceen buffer to the decompresser
682 	InitBMV(screenBuffer);
683 
684 	// Initialize some stuff
685 	nextUseOffset = 0;
686 	nextSoundOffset = 0;
687 	wrapUseOffset = -1;
688 	mostFutureOffset = 0;
689 	currentFrame = 0;
690 	currentSoundFrame = 0;
691 	numAdvancePackets = 0;
692 	nextReadSlot = 0;
693 	bFileEnd = false;
694 	blobsInBuffer = 0;
695 	memset(texts, 0, sizeof(texts));
696 	talkColor = 0;
697 	bigProblemCount = 0;
698 
699 	movieTick = 0;
700 
701 	bIsText = false;
702 
703 	// Prefetch data
704 	LoadSlots(PREFETCH);
705 
706 	while (numAdvancePackets < ADVANCE_SOUND)
707 		LoadSlots(1);
708 
709 	// Initialize the sound channel
710 	InitializeMovieSound();
711 }
712 
713 /**
714  * Called from the foreground when ending playback of a movie.
715  */
FinishBMV()716 void BMVPlayer::FinishBMV() {
717 	int	i;
718 
719 	// Notify the sound channel
720 	FinishMovieSound();
721 
722 	// Close the file stream
723 	if (stream.isOpen())
724 		stream.close();
725 
726 	// Release the data buffer
727 	free(bigBuffer);
728 	bigBuffer = NULL;
729 
730 	// Release the screen buffer
731 	free(screenBuffer);
732 	screenBuffer = NULL;
733 
734 	// Ditch any text objects
735 	for (i = 0; i < 2; i++) {
736 		if (texts[i].pText) {
737 			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), texts[i].pText);
738 			texts[i].pText = NULL;
739 		}
740 	}
741 	bMovieOn = false;
742 
743 	nextMovieTime = 0;
744 
745 	// Test for 'twixt-movie glitch
746 	ClearScreen();
747 }
748 
749 /**
750  * MaintainBuffer()
751  */
MaintainBuffer()752 bool BMVPlayer::MaintainBuffer() {
753 	int nextOffset;
754 
755 	// No action if the file is all read
756 	if (bFileEnd == true)
757 		return false;
758 
759 	// See if next complete packet exists
760 	// and if so, if it will fit in the top of the buffer
761 	nextOffset = FollowingPacket(mostFutureOffset, false);
762 	if (nextOffset == -1) {
763 		// No following packets
764 		return false;
765 	} else if (nextOffset > NUM_SLOTS * SLOT_SIZE) {
766 		// The current unfinished packet will not fit
767 		// Copy this slot to slot 0
768 
769 		// Not if we're still using it!!!
770 		// Or, indeed, if the player is still lagging
771 		if (nextUseOffset < SLOT_SIZE || nextUseOffset > mostFutureOffset) {
772 			// Slot 0 is still in use, buffer is full!
773 			return false;
774 		}
775 
776 		// Tell data player where to make the jump
777 		wrapUseOffset = mostFutureOffset;
778 
779 		// mostFuture Offset is now in slot 0
780 		mostFutureOffset %= SLOT_SIZE;
781 
782 		// Copy the data we already have for unfinished packet
783 		memcpy(bigBuffer + mostFutureOffset,
784 			bigBuffer + wrapUseOffset,
785 			SLOT_SIZE - mostFutureOffset);
786 
787 		// Next read is into slot 1
788 		nextReadSlot = 1;
789 	}
790 
791 	if (nextReadSlot == NUM_SLOTS) {
792 		// Want to go to slot zero, wait if still in use
793 		if (nextUseOffset < SLOT_SIZE) {
794 			// Slot 0 is still in use, buffer is full!
795 			return false;
796 		}
797 
798 		// nextOffset must be the buffer size
799 		assert(nextOffset == NUM_SLOTS*SLOT_SIZE);
800 
801 		// wrapUseOffset must not be set
802 		assert(wrapUseOffset == -1);
803 		wrapUseOffset = nextOffset;
804 
805 		nextReadSlot = 0;
806 		mostFutureOffset = 0;
807 	}
808 
809 	// Don't overwrite unused data
810 	if (nextUseOffset / SLOT_SIZE == nextReadSlot) {
811 		// Buffer is full!
812 		return false;
813 	}
814 
815 	if (stream.read(bigBuffer + nextReadSlot * SLOT_SIZE, SLOT_SIZE) != SLOT_SIZE) {
816 		bFileEnd = true;
817 	}
818 
819 	// Read next slot next time
820 	nextReadSlot++;
821 
822 	// Find new mostFutureOffset
823 	nextOffset = FollowingPacket(mostFutureOffset, false);
824 	while (nextOffset < nextReadSlot*SLOT_SIZE
825 			&& nextOffset != -1) {
826 		numAdvancePackets++;
827 		mostFutureOffset = nextOffset;
828 		nextOffset = FollowingPacket(mostFutureOffset, false);
829 	}
830 
831 	// New test feature for e.g. short files
832 	if (bFileEnd && *(bigBuffer+mostFutureOffset) != CD_LE_FIN)
833 		bAbort = true;
834 
835 	return true;
836 }
837 
838 /**
839  * DoBMVFrame
840  */
DoBMVFrame()841 bool BMVPlayer::DoBMVFrame() {
842 	unsigned char *data;
843 	int	graphOffset, length;
844 	signed short	xscr;
845 
846 	if (nextUseOffset == wrapUseOffset) {
847 		nextUseOffset %= SLOT_SIZE;
848 	}
849 
850 	while (nextUseOffset == mostFutureOffset) {
851 		data = bigBuffer + nextUseOffset;
852 		if (*data != CD_LE_FIN) {
853 			// Don't get stuck in an infinite loop
854 			if (!MaintainBuffer()) {
855 				FinishBMV();
856 				return false;
857 			}
858 
859 			if (nextUseOffset == wrapUseOffset) {
860 				nextUseOffset %= SLOT_SIZE;
861 			}
862 		} else
863 			break;
864 	}
865 
866 	// Set pointer to data
867 	data = bigBuffer + nextUseOffset;
868 
869 	// If still at most Future, it must be last
870 	if (nextUseOffset == mostFutureOffset) {
871 		assert(*data == CD_LE_FIN);
872 	}
873 
874 	switch (*data) {
875 	case CD_SLOT_NOP:
876 		nextUseOffset = FollowingPacket(nextUseOffset, true);
877 		if (nextUseOffset == wrapUseOffset) {
878 			nextUseOffset %= SLOT_SIZE;
879 			wrapUseOffset = -1;
880 		}
881 		numAdvancePackets--;
882 		return false;
883 
884 	case CD_LE_FIN:
885 		FinishBMV();
886 		numAdvancePackets--;
887 		return true;
888 
889 	default:
890 		length = (int32)READ_32(data + 1);
891 		length &= 0x00ffffff;
892 
893 		graphOffset = nextUseOffset + 4;	// Skip command byte and length
894 
895 		if (*data & CD_AUDIO) {
896 			if (bOldAudio) {
897 				graphOffset += sz_AUDIO_pkt;	// Skip audio data
898 				length -= sz_AUDIO_pkt;
899 			} else {
900 				int blobs;
901 
902 				blobs = *(bigBuffer + graphOffset);
903 				blobs *= SZ_C_BLOB;
904 				graphOffset += (blobs + 1);
905 				length -= (blobs + 1);
906 			}
907 		}
908 
909 		if (*data & CD_CMND) {
910 			int cmdLen;
911 
912 			// Process command and skip data
913 			cmdLen = MovieCommand(*data, graphOffset);
914 
915 			graphOffset += cmdLen;
916 			length -= cmdLen;
917 		}
918 
919 		if (*data & CD_CMAP) {
920 			MoviePalette(graphOffset);
921 			graphOffset += sz_CMAP_pkt;	// Skip palette data
922 			length -= sz_CMAP_pkt;
923 		}
924 
925 		if (*data & CD_XSCR) {
926 			xscr = (int16)READ_16(bigBuffer + graphOffset);
927 			graphOffset += sz_XSCR_pkt;	// Skip scroll offset
928 			length -= sz_XSCR_pkt;
929 		} else if (*data & BIT0)
930 			xscr = -640;
931 		else
932 			xscr = 0;
933 
934 		PrepBMV(ScreenBeg, bigBuffer + graphOffset, length, xscr);
935 
936 		currentFrame++;
937 		numAdvancePackets--;
938 
939 		nextUseOffset = FollowingPacket(nextUseOffset, true);
940 		if (nextUseOffset == wrapUseOffset) {
941 			nextUseOffset %= SLOT_SIZE;
942 			wrapUseOffset = -1;
943 		}
944 		return true;
945 	}
946 }
947 
948 /**
949  * DoSoundFrame
950  */
DoSoundFrame()951 bool BMVPlayer::DoSoundFrame() {
952 	unsigned char *data;
953 	int	graphOffset;
954 
955 	if (nextSoundOffset == wrapUseOffset) {
956 		nextSoundOffset %= SLOT_SIZE;
957 	}
958 
959 	// Make sure the full slot is here
960 	while (nextSoundOffset == mostFutureOffset) {
961 		data = bigBuffer + nextSoundOffset;
962 		if (*data != CD_LE_FIN) {
963 			// Don't get stuck in an infinite loop
964 			if (!MaintainBuffer()) {
965 				if (!bOldAudio)
966 					MovieAudio(0, 0);
967 				currentSoundFrame++;
968 				return false;
969 			}
970 
971 			if (nextSoundOffset == wrapUseOffset) {
972 				nextSoundOffset %= SLOT_SIZE;
973 			}
974 		} else
975 			break;
976 	}
977 
978 	// Set pointer to data
979 	data = bigBuffer + nextSoundOffset;
980 
981 	// If still at most Future, it must be last
982 	if (nextSoundOffset == mostFutureOffset) {
983 		assert(*data == CD_LE_FIN);
984 	}
985 
986 	switch (*data) {
987 	case CD_SLOT_NOP:
988 		nextSoundOffset = FollowingPacket(nextSoundOffset, true);
989 		if (nextSoundOffset == wrapUseOffset) {
990 			nextSoundOffset %= SLOT_SIZE;
991 		}
992 		return false;
993 
994 	case CD_LE_FIN:
995 		if (!bOldAudio)
996 			MovieAudio(0, 0);
997 		currentSoundFrame++;
998 		return true;
999 
1000 	default:
1001 		if (*data & CD_AUDIO) {
1002 			graphOffset = nextSoundOffset + 4;	// Skip command byte and length
1003 
1004 			if (!bOldAudio) {
1005 				int blobs = *(bigBuffer + graphOffset);
1006 				MovieAudio(graphOffset+1, blobs);
1007 			}
1008 		} else {
1009 			if (!bOldAudio)
1010 				MovieAudio(0, 0);
1011 		}
1012 
1013 		nextSoundOffset = FollowingPacket(nextSoundOffset, false);
1014 		if (nextSoundOffset == wrapUseOffset) {
1015 			nextSoundOffset %= SLOT_SIZE;
1016 		}
1017 		currentSoundFrame++;
1018 		return true;
1019 	}
1020 }
1021 
1022 /**
1023  * CopyMovieToScreen
1024  */
CopyMovieToScreen()1025 void BMVPlayer::CopyMovieToScreen() {
1026 	// Not if not up and running yet!
1027 	if (!screenBuffer || (currentFrame == 0)) {
1028 		DrawBackgnd();
1029 		return;
1030 	}
1031 
1032 	// The movie surface is slightly less high than the output screen (429 rows versus 432).
1033 	// Because of this, there's some extra line clearing above and below the displayed area
1034 	int yStart = (SCREEN_HEIGHT - SCREEN_HIGH) / 2;
1035 	memset(_vm->screen().getPixels(), 0, yStart * SCREEN_WIDTH);
1036 	memcpy(_vm->screen().getBasePtr(0, yStart), ScreenBeg, SCREEN_WIDTH * SCREEN_HIGH);
1037 	memset(_vm->screen().getBasePtr(0, yStart + SCREEN_HIGH), 0,
1038 		(SCREEN_HEIGHT - SCREEN_HIGH - yStart) * SCREEN_WIDTH);
1039 
1040 	BmvDrawText(true);
1041 	PalettesToVideoDAC();			// Keep palette up-to-date
1042 	UpdateScreenRect(Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
1043 	g_system->updateScreen();
1044 	BmvDrawText(false);
1045 }
1046 
1047 /**
1048  * Handles playback of any active movie. Called from the foreground 24 times a second.
1049  */
FettleBMV()1050 void BMVPlayer::FettleBMV() {
1051 
1052 	int refFrame;
1053 	// Tick counter needs to be incrementing at a 24Hz rate
1054 	int tick = movieTick++;
1055 
1056 	if (!bMovieOn)
1057 		return;
1058 
1059 	// Escape the rest if appropriate
1060 	if (bAbort || (bmvEscape && bmvEscape != GetEscEvents())) {
1061 		FinishBMV();
1062 		return;
1063 	}
1064 
1065 	if (!stream.isOpen()) {
1066 		int i;
1067 
1068 		// First time in with this movie
1069 
1070 		InitializeBMV();
1071 
1072 		for (i = 0; i < ADVANCE_SOUND;) {
1073 			if (DoSoundFrame())
1074 				i++;
1075 		}
1076 		startTick = -ONE_SECOND / 4;	// 1/4 second
1077 		return;
1078 	}
1079 
1080 	if (startTick < 0) {
1081 		startTick++;
1082 		return;
1083 	}
1084 	if (startTick == 0) {
1085 		startTick = tick;
1086 		nextMaintain = startTick + 1;
1087 		StartMovieSound();
1088 	}
1089 
1090 	nextMovieTime = g_system->getMillis() + 41;
1091 
1092 	FettleMovieText();
1093 
1094 	if (bigProblemCount < PT_A) {
1095 		refFrame = currentSoundFrame;
1096 
1097 		while (currentSoundFrame < ((tick+1-startTick)/2 + ADVANCE_SOUND) && bMovieOn) {
1098 			if (currentSoundFrame == refFrame+PT_B)
1099 				break;
1100 
1101 			DoSoundFrame();
1102 		}
1103 	}
1104 
1105 	// Time to process a frame (or maybe more)
1106 	if (bigProblemCount < PT_A) {
1107 		refFrame = currentFrame;
1108 
1109 		while ((currentFrame < (tick-startTick)/2) && bMovieOn) {
1110 			DoBMVFrame();
1111 
1112 			if (currentFrame == refFrame+PT_B) {
1113 				bigProblemCount++;
1114 
1115 				if (bigProblemCount == PT_A) {
1116 					startTick = tick-(2*currentFrame);
1117 					bigProblemCount = 0;
1118 				}
1119 				break;
1120 			}
1121 		}
1122 		if (currentFrame == refFrame || currentFrame <= refFrame+3) {
1123 			bigProblemCount = 0;
1124 		}
1125 	} else {
1126 		while (currentFrame < (tick-startTick)/2 && bMovieOn) {
1127 			DoBMVFrame();
1128 		}
1129 	}
1130 
1131 	if (tick >= nextMaintain || numAdvancePackets < SUBSEQUENT_SOUND) {
1132 		MaintainBuffer();
1133 		nextMaintain = tick + 2;
1134 	}
1135 }
1136 
1137 /**
1138  * Returns true if a movie is playing.
1139  */
MoviePlaying()1140 bool BMVPlayer::MoviePlaying() {
1141 	return bMovieOn;
1142 }
1143 
1144 /**
1145  * Returns the audio lag in ms
1146  */
MovieAudioLag()1147 int32 BMVPlayer::MovieAudioLag() {
1148 	if (!bMovieOn || !_audioStream)
1149 		return 0;
1150 
1151 	// Calculate lag
1152 	int32 playLength = (movieTick - startTick - 1) * ((((uint32) 1000) << 10) / 24);
1153 	return (playLength - (((int32) _vm->_mixer->getSoundElapsedTime(_audioHandle)) << 10)) >> 10;
1154 }
1155 
NextMovieTime()1156 uint32 BMVPlayer::NextMovieTime() {
1157 	return nextMovieTime;
1158 }
1159 
AbortMovie()1160 void BMVPlayer::AbortMovie() {
1161 	bAbort = true;
1162 }
1163 
1164 } // End of namespace Tinsel
1165