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