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 */
22
23 #include "groovie/vdx.h"
24 #include "groovie/graphics.h"
25 #include "groovie/groovie.h"
26 #include "groovie/lzss.h"
27
28 #include "common/debug.h"
29 #include "common/debug-channels.h"
30 #include "common/textconsole.h"
31 #include "audio/audiostream.h"
32 #include "audio/mixer.h"
33 #include "audio/decoders/raw.h"
34 #include "graphics/palette.h"
35
36 #define TILE_SIZE 4 // Size of each tile on the image: only ever seen 4 so far
37 #define VDX_IDENT 0x9267 // 37479
38
39 namespace Groovie {
40
VDXPlayer(GroovieEngine * vm)41 VDXPlayer::VDXPlayer(GroovieEngine *vm) :
42 VideoPlayer(vm), _origX(0), _origY(0), _flagOnePrev(false),
43 _fg(&_vm->_graphicsMan->_foreground), _bg(&_vm->_graphicsMan->_background) {
44 }
45
~VDXPlayer()46 VDXPlayer::~VDXPlayer() {
47 //delete _audioStream;
48 }
49
resetFlags()50 void VDXPlayer::resetFlags() {
51 _flagOnePrev = false;
52 }
53
setOrigin(int16 x,int16 y)54 void VDXPlayer::setOrigin(int16 x, int16 y) {
55 _origX = x;
56 _origY = y;
57 }
58
loadInternal()59 uint16 VDXPlayer::loadInternal() {
60 if (DebugMan.isDebugChannelEnabled(kDebugVideo)) {
61 int8 i;
62 debugN(1, "Groovie::VDX: New VDX: bitflags are ");
63 for (i = 15; i >= 0; i--) {
64 debugN(1, "%d", _flags & (1 << i)? 1 : 0);
65 if (i % 4 == 0) {
66 debugN(1, " ");
67 }
68 }
69 debug(1, " <- 0 ");
70 }
71 // Flags:
72 // - 1 Puzzle piece? Skip palette, don't redraw full screen, draw still to b/ack buffer
73 // - 2 Transparent color is 0xFF
74 // - 5 Skip still chunks
75 // - 7
76 // - 8 Just show the first frame
77 // - 9 Start a palette fade in
78 _flagZero = ((_flags & (1 << 0)) != 0);
79 _flagOne = ((_flags & (1 << 1)) != 0);
80 _flag2Byte = (_flags & (1 << 2)) ? 0xFF : 0x00;
81 _flagThree = ((_flags & (1 << 3)) != 0);
82 _flagFour = ((_flags & (1 << 4)) != 0);
83 _flagFive = ((_flags & (1 << 5)) != 0);
84 _flagSix = ((_flags & (1 << 6)) != 0);
85 _flagSeven = ((_flags & (1 << 7)) != 0);
86 _flagEight = ((_flags & (1 << 8)) != 0);
87 _flagNine = ((_flags & (1 << 9)) != 0);
88
89 // Enable highspeed if we're not obeying fps, and not marked as special
90 // This will be disabled in chunk audio if we're actually an audio vdx
91 if (_vm->_modeSpeed == kGroovieSpeedFast && ((_flags & (1 << 15)) == 0))
92 setOverrideSpeed(true);
93
94 if (_flagOnePrev && !_flagOne && !_flagEight) {
95 _flagSeven = true;
96 }
97
98 // Save _flagOne for the next video
99 _flagOnePrev = _flagOne;
100
101 //_flagTransparent = _flagOne;
102 _flagFirstFrame = _flagEight;
103 //_flagSkipPalette = _flagSeven;
104 _flagSkipPalette = false;
105 //_flagSkipStill = _flagFive || _flagSeven;
106 //_flagUpdateStill = _flagNine || _flagSix;
107
108 // Begin reading the file
109 debugC(1, kDebugVideo, "Groovie::VDX: Playing video");
110
111 if (_file->readUint16LE() != VDX_IDENT) {
112 error("Groovie::VDX: This does not appear to be a 7th guest VDX file");
113 return 0;
114 } else {
115 debugC(5, kDebugVideo, "Groovie::VDX: VDX file identified correctly");
116 }
117
118 uint16 tmp;
119
120 // Skip unknown data: 6 bytes, ref Martine
121 tmp = _file->readUint16LE();
122 debugC(2, kDebugVideo | kDebugUnknown, "Groovie::VDX: Martine1 = 0x%04X", tmp);
123 tmp = _file->readUint16LE();
124 debugC(2, kDebugVideo | kDebugUnknown, "Groovie::VDX: Martine2 = 0x%04X", tmp);
125 tmp = _file->readUint16LE();
126 debugC(2, kDebugVideo | kDebugUnknown, "Groovie::VDX: Martine3 (FPS?) = %d", tmp);
127
128 return tmp;
129 }
130
playFrameInternal()131 bool VDXPlayer::playFrameInternal() {
132 byte currRes = 0x80;
133 Common::ReadStream *vdxData = 0;
134 while (currRes == 0x80) {
135 currRes = _file->readByte();
136
137 // Skip unknown data: 1 byte, ref Edward
138 byte tmp = _file->readByte();
139
140 uint32 compSize = _file->readUint32LE();
141 uint8 lengthmask = _file->readByte();
142 uint8 lengthbits = _file->readByte();
143
144 if (_file->eos())
145 break;
146
147 debugC(5, kDebugVideo | kDebugUnknown, "Groovie::VDX: Edward = 0x%04X", tmp);
148
149 // Read the chunk data and decompress if needed
150 if (compSize)
151 vdxData = _file->readStream(compSize);
152
153 if (lengthmask && lengthbits) {
154 Common::ReadStream *decompData = new LzssReadStream(vdxData, lengthmask, lengthbits);
155 delete vdxData;
156 vdxData = decompData;
157 }
158
159 // Use the current chunk
160 switch (currRes) {
161 case 0x00:
162 debugC(6, kDebugVideo, "Groovie::VDX: Replay frame");
163 break;
164 case 0x20:
165 debugC(5, kDebugVideo, "Groovie::VDX: Still frame");
166 getStill(vdxData);
167 break;
168 case 0x25:
169 debugC(5, kDebugVideo, "Groovie::VDX: Animation frame");
170 getDelta(vdxData);
171 break;
172 case 0x80:
173 debugC(5, kDebugVideo, "Groovie::VDX: Sound resource");
174 chunkSound(vdxData);
175 break;
176 default:
177 error("Groovie::VDX: Invalid resource type: %d", currRes);
178 }
179 delete vdxData;
180 vdxData = 0;
181 }
182
183 // Wait until the current frame can be shown
184
185 if (!DebugMan.isDebugChannelEnabled(kDebugFast)) {
186 waitFrame();
187 }
188 // TODO: Move it to a better place
189 // Update the screen
190 if (currRes == 0x25) {
191 //if (_flagSeven) {
192 //_vm->_graphicsMan->mergeFgAndBg();
193 //}
194 _vm->_graphicsMan->updateScreen(_bg);
195 }
196
197 // Report the end of the video if we reached the end of the file or if we
198 // just wanted to play one frame.
199 if (_file->eos() || _flagFirstFrame) {
200 _origX = _origY = 0;
201 return 1;
202 } else {
203 return 0;
204 }
205 }
206
207 static const uint16 vdxBlockMapLookup[] = {
208 0xc800, 0xec80, 0xfec8, 0xffec, 0xfffe, 0x3100, 0x7310, 0xf731, 0xff73, 0xfff7, 0x6c80, 0x36c8, 0x136c, 0x6310, 0xc631, 0x8c63,
209 0xf000, 0xff00, 0xfff0, 0x1111, 0x3333, 0x7777, 0x6666, 0xcccc, 0x0ff0, 0x00ff, 0xffcc, 0x0076, 0xff33, 0x0ee6, 0xccff, 0x6770,
210 0x33ff, 0x6ee0, 0x4800, 0x2480, 0x1248, 0x0024, 0x0012, 0x2100, 0x4210, 0x8421, 0x0042, 0x0084, 0xf888, 0x0044, 0x0032, 0x111f,
211 0x22e0, 0x4c00, 0x888f, 0x4470, 0x2300, 0xf111, 0x0e22, 0x00c4, 0xf33f, 0xfccf, 0xff99, 0x99ff, 0x4444, 0x2222, 0xccee, 0x7733,
212 0x00f8, 0x00f1, 0x00bb, 0x0cdd, 0x0f0f, 0x0f88, 0x13f1, 0x19b3, 0x1f80, 0x226f, 0x27ec, 0x3077, 0x3267, 0x37e4, 0x38e3, 0x3f90,
213 0x44cf, 0x4cd9, 0x4c99, 0x5555, 0x603f, 0x6077, 0x6237, 0x64c9, 0x64cd, 0x6cd9, 0x70ef, 0x0f00, 0x00f0, 0x0000, 0x4444, 0x2222
214 };
215
getDelta(Common::ReadStream * in)216 void VDXPlayer::getDelta(Common::ReadStream *in) {
217 uint16 k, l;
218
219 // Get the size of the local palette
220 uint16 palSize = in->readUint16LE();
221
222 // Load the palette if it isn't empty
223 if (palSize) {
224 uint16 palBitField[16];
225
226 // Load the bit field
227 for (l = 0; l < 16; l++) {
228 palBitField[l] = in->readUint16LE();
229 }
230
231 // Load the actual palette
232 for (l = 0; l < 16; l++) {
233 int flag = 1 << 15;
234 for (uint16 j = 0; j < 16; j++) {
235 int palIndex = (l * 16) + j;
236
237 if (flag & palBitField[l]) {
238 for (k = 0; k < 3; k++) {
239 _palBuf[(palIndex * 3) + k] = in->readByte();
240 }
241 }
242 flag = flag >> 1;
243 }
244 }
245
246 // Apply the palette
247 if (!_flagSeven) {
248 //if (!_flagSix && !_flagSeven) {
249 setPalette(_palBuf);
250 }
251 }
252
253 uint8 currOpCode = in->readByte();
254 uint8 param1, param2, param3;
255
256 uint16 currentLine = 0;
257 uint32 offset = 0;
258 while (!in->eos()) {
259 byte colors[16];
260 if (currOpCode < 0x60) {
261 param1 = in->readByte();
262 param2 = in->readByte();
263 expandColorMap(colors, vdxBlockMapLookup[currOpCode], param1, param2);
264 decodeBlockDelta(offset, colors, 640);
265 offset += TILE_SIZE;
266 } else if (currOpCode > 0x7f) {
267 param1 = in->readByte();
268 param2 = in->readByte();
269 param3 = in->readByte();
270 expandColorMap(colors, (param1 << 8) + currOpCode, param2, param3);
271 decodeBlockDelta(offset, colors, 640);
272 offset += TILE_SIZE;
273 } else switch (currOpCode) {
274 case 0x60: /* Fill tile with the 16 colors given as parameters */
275 for (l = 0; l < 16; l++) {
276 colors[l] = in->readByte();
277 }
278 decodeBlockDelta(offset, colors, 640);
279 offset += TILE_SIZE;
280 break;
281 case 0x61: /* Skip to the end of this line, next block is start of next */
282 /* Note this is used at the end of EVERY line */
283 currentLine++;
284 offset = currentLine * TILE_SIZE * 640;
285 break;
286 case 0x62:
287 case 0x63:
288 case 0x64:
289 case 0x65:
290 case 0x66:
291 case 0x67:
292 case 0x68:
293 case 0x69:
294 case 0x6a:
295 case 0x6b: /* Skip next param1 blocks (within line) */
296 offset += (currOpCode - 0x62) * TILE_SIZE;
297 break;
298 case 0x6c:
299 case 0x6d:
300 case 0x6e:
301 case 0x6f:
302 case 0x70:
303 case 0x71:
304 case 0x72:
305 case 0x73:
306 case 0x74:
307 case 0x75: /* Next param1 blocks are filled with color param2 */
308 param1 = currOpCode - 0x6b;
309 param2 = in->readByte();
310 for (l = 0; l < 16; l++) {
311 colors[l] = param2;
312 }
313 for (k = 0; k < param1; k++) {
314 decodeBlockDelta(offset, colors, 640);
315 offset += TILE_SIZE;
316 }
317 break;
318 case 0x76:
319 case 0x77:
320 case 0x78:
321 case 0x79:
322 case 0x7a:
323 case 0x7b:
324 case 0x7c:
325 case 0x7d:
326 case 0x7e:
327 case 0x7f: /* Next bytes contain colors to fill the next param1 blocks in the current line*/
328 param1 = currOpCode - 0x75;
329 for (k = 0; k < param1; k++) {
330 param2 = in->readByte();
331 for (l = 0; l < 16; l++) {
332 colors[l] = param2;
333 }
334 decodeBlockDelta(offset, colors, 640);
335 offset += TILE_SIZE;
336 }
337 break;
338 default:
339 error("Groovie::VDX: Broken somehow");
340 }
341 currOpCode = in->readByte();
342 }
343 }
344
getStill(Common::ReadStream * in)345 void VDXPlayer::getStill(Common::ReadStream *in) {
346 uint16 numXTiles = in->readUint16LE();
347 debugC(5, kDebugVideo, "Groovie::VDX: numXTiles=%d", numXTiles);
348 uint16 numYTiles = in->readUint16LE();
349 debugC(5, kDebugVideo, "Groovie::VDX: numYTiles=%d", numYTiles);
350
351 // It's skipped in the original:
352 uint16 colorDepth = in->readUint16LE();
353 debugC(5, kDebugVideo, "Groovie::VDX: colorDepth=%d", colorDepth);
354
355 uint16 imageWidth = TILE_SIZE * numXTiles;
356
357 uint8 mask = 0;
358 byte *buf;
359 if (_flagOne) {
360 // Paint to the foreground
361 buf = (byte *)_fg->getPixels();
362 if (_flag2Byte) {
363 mask = 0xff;
364 } else {
365 mask = 0;
366 }
367
368 // TODO: Verify this is the right procedure. Couldn't find it on the
369 // disassembly, but it's required to work properly
370 _flagFirstFrame = true;
371 } else {
372 // Paint to the background
373 buf = (byte *)_bg->getPixels();
374 }
375
376 // Read the palette
377 in->read(_palBuf, 3 * 256);
378
379 if (_flagSeven) {
380 _flagFive = true;
381 }
382
383 // Skip the frame when flag 5 is set, unless flag 1 is set
384 if (!_flagFive || _flagOne) {
385
386 byte colors[16];
387 for (uint16 j = 0; j < numYTiles; j++) {
388 byte *currentTile = buf + j * TILE_SIZE * imageWidth;
389 for (uint16 i = numXTiles; i; i--) {
390 uint8 color1 = in->readByte();
391 uint8 color0 = in->readByte();
392 uint16 colorMap = in->readUint16LE();
393 expandColorMap(colors, colorMap, color1, color0);
394 decodeBlockStill(currentTile, colors, 640, mask);
395
396 currentTile += TILE_SIZE;
397 }
398 }
399
400 // Apply the palette
401 if (_flagNine) {
402 // Flag 9 starts a fade in
403 fadeIn(_palBuf);
404 } else {
405 if (!_flagOne && !_flagSeven) {
406 // Actually apply the palette
407 setPalette(_palBuf);
408 }
409 }
410
411 if (!_flagOne) {
412 _vm->_graphicsMan->updateScreen(_bg);
413 }
414 /*
415 if (_flagSix) {
416 if (_flagOne) {
417 _vm->_graphicsMan->updateScreen(_fg);
418 } else {
419 _vm->_graphicsMan->updateScreen(_bg);
420 }
421 _flagSix = 0;
422 }
423 */
424 } else {
425 // Skip the remaining data
426 debugC(10, kDebugVideo, "Groovie::VDX: Skipping still frame");
427 while (!in->eos()) {
428 in->readByte();
429 }
430 }
431 }
432
expandColorMap(byte * out,uint16 colorMap,uint8 color1,uint8 color0)433 void VDXPlayer::expandColorMap(byte *out, uint16 colorMap, uint8 color1, uint8 color0) {
434 // It's a bit faster to start from the end
435 out += 16;
436 for (int i = 16; i; i--) {
437 // Set the corresponding color
438 // The following is an optimized version of:
439 // *--out = (colorMap & 1) ? color1 : color0;
440 uint8 selector = -(colorMap & 1);
441 *--out = (selector & color1) | (~selector & color0);
442
443 // Update the flag map to test the next color
444 colorMap >>= 1;
445 }
446 }
447
decodeBlockStill(byte * buf,byte * colors,uint16 imageWidth,uint8 mask)448 void VDXPlayer::decodeBlockStill(byte *buf, byte *colors, uint16 imageWidth, uint8 mask) {
449 assert(TILE_SIZE == 4);
450
451 for (int y = TILE_SIZE; y; y--) {
452 if (_flagOne) {
453 // TODO: optimize with bit logic?
454 for (int x = 0; x < TILE_SIZE; x++) {
455 // 0xff pixels don't modify the buffer
456 if (*colors != 0xff) {
457 // Write the color
458 *buf = *colors | mask;
459 // Note: if the mask is 0, it paints the image
460 // else, it paints the image's mask using 0xff
461 }
462
463 // Point to the next color
464 colors++;
465
466 // Point to the next pixel
467 buf++;
468 }
469
470 // Point to the start of the next line
471 buf += imageWidth - TILE_SIZE;
472 } else {
473 *((uint32 *)buf) = *((uint32 *)colors);
474 colors += 4;
475
476 // Point to the start of the next line
477 buf += imageWidth;
478 }
479 }
480 }
481
decodeBlockDelta(uint32 offset,byte * colors,uint16 imageWidth)482 void VDXPlayer::decodeBlockDelta(uint32 offset, byte *colors, uint16 imageWidth) {
483 assert(TILE_SIZE == 4);
484
485 byte *dest;
486 // TODO: Verify just the else block is required
487 //if (_flagOne) {
488 // Paint to the foreground
489 //dest = (byte *)_fg->getPixels() + offset;
490 //} else {
491 dest = (byte *)_bg->getPixels() + offset;
492 //}
493
494 // Move the pointers to the beginning of the current block
495 int32 blockOff = _origX + _origY * imageWidth;
496 dest += blockOff;
497 byte *fgBuf = 0;
498 if (_flagSeven) {
499 fgBuf = (byte *)_fg->getPixels() + offset + blockOff;
500 //byte *bgBuf = (byte *)_bg->getPixels() + offset + blockOff;
501 }
502
503 for (int y = TILE_SIZE; y; y--) {
504 if (_flagSeven) {
505 // Paint mask
506 for (int x = 0; x < TILE_SIZE; x++) {
507 // TODO: this can probably be optimized with bit logic
508 if (fgBuf[x] != 0xff) {
509 if (*colors == 0xff) {
510 dest[x] = fgBuf[x];
511 } else {
512 dest[x] = *colors;
513 }
514 }
515 colors++;
516 }
517 fgBuf += imageWidth;
518 } else {
519 // Paint directly
520 *((uint32 *)dest) = *((uint32 *)colors);
521 colors += 4;
522 }
523
524 // Move to the next line
525 dest += imageWidth;
526 }
527 }
528
chunkSound(Common::ReadStream * in)529 void VDXPlayer::chunkSound(Common::ReadStream *in) {
530 if (getOverrideSpeed())
531 setOverrideSpeed(false);
532
533 if (!_audioStream) {
534 _audioStream = Audio::makeQueuingAudioStream(22050, false);
535 Audio::SoundHandle sound_handle;
536 g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &sound_handle, _audioStream);
537 }
538
539 byte *data = (byte *)malloc(60000);
540 int chunksize = in->read(data, 60000);
541 if (!DebugMan.isDebugChannelEnabled(kDebugFast)) {
542 _audioStream->queueBuffer(data, chunksize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
543 }
544 }
545
fadeIn(uint8 * targetpal)546 void VDXPlayer::fadeIn(uint8 *targetpal) {
547 // Don't do anything if we're asked to skip palette changes
548 if (_flagSkipPalette)
549 return;
550
551 // TODO: Is it required? If so, move to an appropiate place
552 // Copy the foreground to the background
553 memcpy((byte *)_vm->_graphicsMan->_foreground.getPixels(), (byte *)_vm->_graphicsMan->_background.getPixels(), 640 * 320);
554
555 // Start a fadein
556 _vm->_graphicsMan->fadeIn(targetpal);
557
558 // Show the background
559 _vm->_graphicsMan->updateScreen(_bg);
560 }
561
setPalette(uint8 * palette)562 void VDXPlayer::setPalette(uint8 *palette) {
563 if (_flagSkipPalette)
564 return;
565
566 debugC(7, kDebugVideo, "Groovie::VDX: Setting palette");
567 _syst->getPaletteManager()->setPalette(palette, 0, 256);
568 }
569
570 } // End of Groovie namespace
571