Ticket #1583: smush_player.cpp

File smush_player.cpp, 25.4 KB (added by SF/dirtyhairy, 16 years ago)
Line 
1/* ScummVM - Scumm Interpreter
2 * Copyright (C) 2002-2004 The ScummVM project
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 *
18 * $Header: /cvsroot/scummvm/scummvm/scumm/smush/smush_player.cpp,v 1.111.2.1 2004/02/29 01:08:17 kirben Exp $
19 *
20 */
21
22#include "stdafx.h"
23
24#include "base/engine.h"
25
26#include "common/config-manager.h"
27#include "common/file.h"
28#include "common/util.h"
29#include "common/timer.h"
30
31#include "scumm/bomp.h"
32#include "scumm/imuse_digi/dimuse.h"
33#include "scumm/imuse.h"
34#include "scumm/scumm.h"
35#include "scumm/sound.h"
36#include "scumm/smush/channel.h"
37#include "scumm/smush/chunk_type.h"
38#include "scumm/smush/chunk.h"
39#include "scumm/smush/smush_font.h"
40#include "scumm/smush/smush_mixer.h"
41#include "scumm/smush/smush_player.h"
42
43#include "scumm/insane/insane.h"
44
45#include "sound/mixer.h"
46
47#ifdef DUMP_SMUSH_FRAMES
48#include <png.h>
49#endif
50
51namespace Scumm {
52
53const int MAX_STRINGS = 200;
54
55class StringResource {
56private:
57
58 struct {
59 int id;
60 char *string;
61 } _strings[MAX_STRINGS];
62
63 int _nbStrings;
64 int _lastId;
65 const char *_lastString;
66
67public:
68
69 StringResource() :
70 _nbStrings(0),
71 _lastId(-1) {
72 };
73 ~StringResource() {
74 for (int32 i = 0; i < _nbStrings; i++) {
75 delete []_strings[i].string;
76 }
77 }
78
79 bool init(char *buffer, int32 length) {
80 char *def_start = strchr(buffer, '#');
81 while (def_start != NULL) {
82 char *def_end = strchr(def_start, '\n');
83 assert(def_end != NULL);
84
85 char *id_end = def_end;
86 while (id_end >= def_start && !isdigit(*(id_end-1))) {
87 id_end--;
88 }
89
90 assert(id_end > def_start);
91 char *id_start = id_end;
92 while (isdigit(*(id_start - 1))) {
93 id_start--;
94 }
95
96 char idstring[32];
97 memcpy(idstring, id_start, id_end - id_start);
98 idstring[id_end - id_start] = 0;
99 int32 id = atoi(idstring);
100 char *data_start = def_end;
101
102 while (*data_start == '\n' || *data_start == '\r') {
103 data_start++;
104 }
105 char *data_end = data_start;
106
107 while (1) {
108 if (data_end[-2] == '\r' && data_end[-1] == '\n' && data_end[0] == '\r' && data_end[1] == '\n') {
109 break;
110 }
111 // In Russian Full Throttle strings are finished with
112 // just one pair of CR-LF
113 if (data_end[-2] == '\r' && data_end[-1] == '\n' && data_end[0] == '#') {
114 break;
115 }
116 data_end++;
117 if (data_end >= buffer + length) {
118 data_end = buffer + length;
119 break;
120 }
121 }
122
123 data_end -= 2;
124 assert(data_end > data_start);
125 char *value = new char[data_end - data_start + 1];
126 assert(value);
127 memcpy(value, data_start, data_end - data_start);
128 value[data_end - data_start] = 0;
129 char *line_start = value;
130 char *line_end;
131
132 while ((line_end = strchr(line_start, '\n'))) {
133 line_start = line_end+1;
134 if (line_start[0] == '/' && line_start[1] == '/') {
135 line_start += 2;
136 if (line_end[-1] == '\r')
137 line_end[-1] = ' ';
138 else
139 *line_end++ = ' ';
140 memmove(line_end, line_start, strlen(line_start)+1);
141 }
142 }
143 _strings[_nbStrings].id = id;
144 _strings[_nbStrings].string = value;
145 _nbStrings ++;
146 def_start = strchr(data_end + 2, '#');
147 }
148 return true;
149 }
150
151 const char *get(int id) {
152 if (id == _lastId) {
153 return _lastString;
154 }
155 debug(9, "StringResource::get(%d)", id);
156 for (int i = 0; i < _nbStrings; i++) {
157 if (_strings[i].id == id) {
158 _lastId = id;
159 _lastString = _strings[i].string;
160 return _strings[i].string;
161 }
162 }
163 warning("invalid string id : %d", id);
164 _lastId = -1;
165 _lastString = "unknown string";
166 return _lastString;
167 }
168};
169
170static StringResource *getStrings(const char *file, const char *directory, bool is_encoded) {
171 debug(7, "trying to read text ressources from %s", file);
172 File theFile;
173
174 theFile.open(file, directory);
175 if (!theFile.isOpen()) {
176 return 0;
177 }
178 int32 length = theFile.size();
179 char *filebuffer = new char [length + 1];
180 assert(filebuffer);
181 theFile.read(filebuffer, length);
182 filebuffer[length] = 0;
183
184 if (is_encoded) {
185 static const int32 ETRS_HEADER_LENGTH = 16;
186 assert(length > ETRS_HEADER_LENGTH);
187 Chunk::type type = READ_BE_UINT32(filebuffer);
188
189 if (type != TYPE_ETRS) {
190 delete [] filebuffer;
191 return getStrings(file, directory, false);
192 }
193
194 char *old = filebuffer;
195 filebuffer = new char[length - ETRS_HEADER_LENGTH + 1];
196 for (int32 i = ETRS_HEADER_LENGTH; i < length; i++) {
197 filebuffer[i - ETRS_HEADER_LENGTH] = old[i] ^ 0xCC;
198 }
199 filebuffer[length - ETRS_HEADER_LENGTH] = '\0';
200 delete []old;
201 length -= ETRS_HEADER_LENGTH;
202 }
203 StringResource *sr = new StringResource;
204 assert(sr);
205 sr->init(filebuffer, length);
206 delete []filebuffer;
207 return sr;
208}
209
210void SmushPlayer::timerCallback(void *refCon) {
211 ((SmushPlayer *)refCon)->parseNextFrame();
212}
213
214SmushPlayer::SmushPlayer(ScummEngine_v6 *scumm, int speed) {
215 _vm = scumm;
216 _version = -1;
217 _nbframes = 0;
218 _smixer = 0;
219 _strings = NULL;
220 _sf[0] = NULL;
221 _sf[1] = NULL;
222 _sf[2] = NULL;
223 _sf[3] = NULL;
224 _sf[4] = NULL;
225 _base = NULL;
226 _frameBuffer = NULL;
227
228 _skipNext = false;
229 _subtitles = ConfMan.getBool("subtitles");
230 _dst = NULL;
231 _storeFrame = false;
232 _width = 0;
233 _height = 0;
234 _IACTpos = 0;
235 _soundFrequency = 22050;
236 _speed = speed;
237 _insanity = false;
238 _middleAudio = false;
239 _skipPalette = false;
240}
241
242SmushPlayer::~SmushPlayer() {
243 deinit();
244}
245
246void SmushPlayer::init() {
247
248 _frame = 0;
249
250 _vm->_videoFinished = false;
251
252 _smixer = new SmushMixer(_vm->_mixer);
253
254 _vm->setDirtyColors(0, 255);
255 _dst = _vm->virtscr[0].screenPtr + _vm->virtscr[0].xstart;
256 g_timer->installTimerProc(&timerCallback, _speed, this);
257
258 _alreadyInit = false;
259}
260
261void SmushPlayer::deinit() {
262 _vm->_timer->removeTimerProc(&timerCallback);
263
264 for (int i = 0; i < 5; i++) {
265 if (_sf[i]) {
266 delete _sf[i];
267 _sf[i] = NULL;
268 }
269 }
270
271 if (_strings) {
272 delete _strings;
273 _strings = NULL;
274 }
275
276 if (_smixer) {
277 _smixer->stop();
278 delete _smixer;
279 _smixer = NULL;
280 }
281
282 if (_base) {
283 delete _base;
284 _base = NULL;
285 }
286
287 _vm->_mixer->stopHandle(_IACTchannel);
288
289 _vm->_fullRedraw = true;
290}
291
292void SmushPlayer::checkBlock(const Chunk &b, Chunk::type type_expected, uint32 min_size) {
293 if (type_expected != b.getType()) {
294 error("Chunk type is different from expected : %x != %x", b.getType(), type_expected);
295 }
296 if (min_size > b.getSize()) {
297 error("Chunk size is inferior than minimum required size : %d < %d", b.getSize(), min_size);
298 }
299}
300
301void SmushPlayer::handleSoundBuffer(int32 track_id, int32 index, int32 max_frames, int32 flags, int32 vol, int32 pan, Chunk &b, int32 size) {
302 debug(6, "SmushPlayer::handleSoundBuffer(%d, %d)", track_id, index);
303// if ((flags & 128) == 128) {
304// return;
305// }
306// if ((flags & 64) == 64) {
307// return;
308// }
309 SmushChannel *c = _smixer->findChannel(track_id);
310 if (c == NULL) {
311 c = new SaudChannel(track_id, _soundFrequency);
312 _smixer->addChannel(c);
313 }
314
315 if ((_middleAudio) && (index != 0)) {
316 c->setParameters(max_frames, flags, vol, pan, index);
317 } else if (index == 0) {
318 c->setParameters(max_frames, flags, vol, pan, index);
319 } else {
320 c->checkParameters(index, max_frames, flags, vol, pan);
321 }
322 _middleAudio = false;
323 c->appendData(b, size);
324}
325
326void SmushPlayer::handleSoundFrame(Chunk &b) {
327 checkBlock(b, TYPE_PSAD);
328 debug(6, "SmushPlayer::handleSoundFrame()");
329
330 int32 track_id = b.getWord();
331 int32 index = b.getWord();
332 int32 max_frames = b.getWord();
333 int32 flags = b.getWord();
334 int32 vol = b.getByte();
335 int32 pan = b.getChar();
336 if (index == 0) {
337 debug(5, "track_id:%d, max_frames:%d, flags:%d, vol:%d, pan:%d", track_id, max_frames, flags, vol, pan);
338 }
339 int32 size = b.getSize() - 10;
340 handleSoundBuffer(track_id, index, max_frames, flags, vol, pan, b, size);
341}
342
343void SmushPlayer::handleSkip(Chunk &b) {
344 checkBlock(b, TYPE_SKIP, 4);
345 int32 code = b.getDword();
346 debug(6, "SmushPlayer::handleSkip(%d)", code);
347 if (code >= 0 && code < 37)
348 _skipNext = _skips[code];
349 else
350 _skipNext = true;
351}
352
353void SmushPlayer::handleStore(Chunk &b) {
354 debug(6, "SmushPlayer::handleStore()");
355 checkBlock(b, TYPE_STOR, 4);
356 _storeFrame = true;
357}
358
359void SmushPlayer::handleFetch(Chunk &b) {
360 debug(6, "SmushPlayer::handleFetch()");
361 checkBlock(b, TYPE_FTCH, 6);
362
363 if (_frameBuffer != NULL) {
364 memcpy(_dst, _frameBuffer, _width * _height);
365 }
366}
367
368void SmushPlayer::handleIACT(Chunk &b) {
369 checkBlock(b, TYPE_IACT, 8);
370 debug(6, "SmushPlayer::handleImuseAction()");
371
372 /* int code = */ b.getWord();
373 int flags = b.getWord();
374 int unknown = b.getShort();
375 int track_flags = b.getWord();
376
377 assert(flags == 46 && unknown == 0);
378 int track_id = b.getWord();
379 int index = b.getWord();
380 int nbframes = b.getWord();
381 int32 size = b.getDword();
382 int32 bsize = b.getSize() - 18;
383
384 if (_vm->_gameId != GID_CMI) {
385 int32 track = track_id;
386 if (track_flags == 1) {
387 track = track_id + 100;
388 } else if (track_flags == 2) {
389 track = track_id + 200;
390 } else if (track_flags == 3) {
391 track = track_id + 300;
392 } else if ((track_flags >= 100) && (track_flags <= 163)) {
393 track = track_id + 400;
394 } else if ((track_flags >= 200) && (track_flags <= 263)) {
395 track = track_id + 500;
396 } else if ((track_flags >= 300) && (track_flags <= 363)) {
397 track = track_id + 600;
398 } else {
399 error("ImuseChannel::handleIACT(): bad track_flags: %d", track_flags);
400 }
401 debug(6, "SmushPlayer::handleIACT(): %d, %d, %d", track, index, track_flags);
402
403 SmushChannel *c = _smixer->findChannel(track);
404 if (c == 0) {
405 c = new ImuseChannel(track, _soundFrequency);
406 _smixer->addChannel(c);
407 }
408 if (index == 0)
409 c->setParameters(nbframes, size, track_flags, unknown, 0);
410 else
411 c->checkParameters(index, nbframes, size, track_flags, unknown);
412 c->appendData(b, bsize);
413 } else {
414 byte output_data[4096];
415 byte *src = (byte *)malloc(bsize);
416 b.read(src, bsize);
417 byte *d_src = src;
418 byte value;
419
420 while (bsize > 0) {
421 if (_IACTpos >= 2) {
422 int32 len = READ_BE_UINT16(_IACToutput) + 2;
423 len -= _IACTpos;
424 if (len > bsize) {
425 memcpy(_IACToutput + _IACTpos, d_src, bsize);
426 _IACTpos += bsize;
427 bsize = 0;
428 } else {
429 memcpy(_IACToutput + _IACTpos, d_src, len);
430 byte *dst = output_data;
431 byte *d_src2 = _IACToutput;
432 d_src2 += 2;
433 int32 count = 1024;
434 byte variable1 = *d_src2++;
435 byte variable2 = variable1 / 16;
436 variable1 &= 0x0f;
437 do {
438 value = *(d_src2++);
439 if (value == 0x80) {
440 *dst++ = *d_src2++;
441 *dst++ = *d_src2++;
442 } else {
443 int16 val = (int8)value << variable2;
444 *dst++ = val >> 8;
445 *dst++ = (byte)(val);
446 }
447 value = *(d_src2++);
448 if (value == 0x80) {
449 *dst++ = *d_src2++;
450 *dst++ = *d_src2++;
451 } else {
452 int16 val = (int8)value << variable1;
453 *dst++ = val >> 8;
454 *dst++ = (byte)(val);
455 }
456 } while (--count);
457
458 if (!_IACTchannel.isActive())
459 _vm->_mixer->newStream(&_IACTchannel, 22050, SoundMixer::FLAG_STEREO | SoundMixer::FLAG_16BITS, 400000);
460 _vm->_mixer->appendStream(_IACTchannel, output_data, 0x1000);
461
462 bsize -= len;
463 d_src += len;
464 _IACTpos = 0;
465 }
466 } else {
467 if (bsize > 1 && _IACTpos == 0) {
468 *(_IACToutput + 0) = *d_src++;
469 _IACTpos = 1;
470 bsize--;
471 }
472 *(_IACToutput + _IACTpos) = *d_src++;
473 _IACTpos++;
474 bsize--;
475 }
476 }
477
478 free(src);
479 }
480}
481
482void SmushPlayer::handleTextResource(Chunk &b) {
483 int pos_x = b.getShort();
484 int pos_y = b.getShort();
485 int flags = b.getShort();
486 int left = b.getShort();
487 int top = b.getShort();
488 int right = b.getShort();
489 /*int32 height =*/ b.getShort();
490 /*int32 unk2 =*/ b.getWord();
491
492 const char *str;
493 char *string = NULL, *string2 = NULL;
494 if (b.getType() == TYPE_TEXT) {
495 string = (char *)malloc(b.getSize() - 16);
496 str = string;
497 b.read(string, b.getSize() - 16);
498 } else {
499 int string_id = b.getWord();
500 if (!_strings)
501 return;
502 str = _strings->get(string_id);
503 }
504
505 // if subtitles disabled and bit 3 is set, then do not draw
506 if ((!_subtitles) && ((flags & 8) == 8))
507 return;
508
509 SmushFont *sf = _sf[0];
510 int color = 15;
511 while (*str == '/') {
512 str++; // For Full Throttle text resources
513 }
514
515 if (_vm->_gameId == GID_CMI) {
516 _vm->translateText((const byte *)str - 1, _vm->_transText);
517 while (*str++ != '/')
518 ;
519 string2 = (char *)_vm->_transText;
520
521 // If string2 contains formatting information there probably
522 // wasn't any translation for it in the language.tab file. In
523 // that case, pretend there is no string2.
524 if (string2[0] == '^')
525 string2[0] = 0;
526 }
527
528 while (str[0] == '^') {
529 switch (str[1]) {
530 case 'f':
531 {
532 int id = str[3] - '0';
533 str += 4;
534 sf = _sf[id];
535 }
536 break;
537 case 'c':
538 {
539 color = str[4] - '0' + 10 *(str[3] - '0');
540 str += 5;
541 }
542 break;
543 default:
544 error("invalid escape code in text string");
545 }
546 }
547
548 assert(sf != NULL);
549 sf->setColor(color);
550
551 if (_vm->_gameId == GID_CMI && string2[0] != 0) {
552 str = string2;
553 }
554
555 // flags:
556 // bit 0 - center 1
557 // bit 1 - not used 2
558 // bit 2 - ??? 4
559 // bit 3 - wrap around 8
560 switch (flags & 9) {
561 case 0:
562 sf->drawStringAbsolute(str, _dst, _width, pos_x, pos_y);
563 break;
564 case 1:
565 sf->drawStringCentered(str, _dst, _width, _height, pos_x, MAX(pos_y, top));
566 break;
567 case 8:
568 // FIXME: Is 'right' the maximum line width here, just
569 // as it is in the next case? It's used several times
570 // in The Dig's intro, where 'left' and 'right' are
571 // always 0 and 321 respectively, and apparently we
572 // handle that correctly.
573 sf->drawStringWrap(str, _dst, _width, _height, pos_x, MAX(pos_y, top), left, right);
574 break;
575 case 9:
576 // In this case, the 'right' parameter is actually the
577 // maximum line width. This explains why it's sometimes
578 // smaller than 'left'.
579 //
580 // Note that in The Dig's "Spacetime Six" movie it's
581 // 621. I have no idea what that means.
582 sf->drawStringWrapCentered(str, _dst, _width, _height, pos_x, MAX(pos_y, top), left, MIN(left + right, _width));
583 break;
584 default:
585 warning("SmushPlayer::handleTextResource. Not handled flags: %d", flags);
586 }
587
588 if (string != NULL) {
589 free (string);
590 }
591}
592
593const char *SmushPlayer::getString(int id) {
594 return _strings->get(id);
595}
596
597bool SmushPlayer::readString(const char *file, const char *directory) {
598 const char *i = strrchr(file, '.');
599 if (i == NULL) {
600 error("invalid filename : %s", file);
601 }
602 char fname[260];
603 memcpy(fname, file, i - file);
604 strcpy(fname + (i - file), ".trs");
605 if ((_strings = getStrings(fname, directory, false)) != 0) {
606 return true;
607 }
608
609 if ((_strings = getStrings("digtxt.trs", directory, true)) != 0) {
610 return true;
611 }
612 return false;
613}
614
615void SmushPlayer::readPalette(byte *out, Chunk &in) {
616 in.read(out, 0x300);
617}
618
619static byte delta_color(byte org_color, int16 delta_color) {
620 int t = (org_color * 129 + delta_color) / 128;
621 if (t > 255)
622 t = 255;
623 if (t < 0)
624 t = 0;
625 return (byte)t;
626}
627
628void SmushPlayer::handleDeltaPalette(Chunk &b) {
629 checkBlock(b, TYPE_XPAL);
630 debug(6, "SmushPlayer::handleDeltaPalette()");
631
632 if (b.getSize() == 0x300 * 3 + 4) {
633
634 b.getWord();
635 b.getWord();
636
637 for (int i = 0; i < 0x300; i++) {
638 _deltaPal[i] = b.getWord();
639 }
640 readPalette(_pal, b);
641 setPalette(_pal);
642 } else if (b.getSize() == 6) {
643
644 b.getWord();
645 b.getWord();
646 b.getWord();
647
648 for (int i = 0; i < 0x300; i++) {
649 _pal[i] = delta_color(_pal[i], _deltaPal[i]);
650 }
651 setPalette(_pal);
652 } else {
653 error("SmushPlayer::handleDeltaPalette() Wrong size for DeltaPalette");
654 }
655}
656
657void SmushPlayer::handleNewPalette(Chunk &b) {
658 checkBlock(b, TYPE_NPAL, 0x300);
659 debug(6, "SmushPlayer::handleNewPalette()");
660
661 if (_skipPalette)
662 return;
663
664 readPalette(_pal, b);
665 setPalette(_pal);
666}
667
668void smush_decode_codec1(byte *dst, byte *src, int left, int top, int height, int width, int dstWidth);
669
670void SmushPlayer::handleFrameObject(Chunk &b) {
671 checkBlock(b, TYPE_FOBJ, 14);
672 if (_skipNext) {
673 _skipNext = false;
674 return;
675 }
676
677 int codec = b.getWord();
678 int left = b.getWord(); // left
679 int top = b.getWord(); // top
680 int width = b.getWord();
681 int height = b.getWord();
682
683 if ((height > _vm->_screenHeight) || (width > _vm->_screenWidth))
684 return;
685
686 // FT Insane uses smaller frames to draw overlays with moving objects
687 // Other .san files do have them as well but their purpose in unknown
688 // and often it causes memory overdraw. So just skip those frames
689 if (!_insanity && ((height != _vm->_screenHeight) || (width != _vm->_screenWidth)))
690 return;
691
692 if (!_alreadyInit) {
693 _codec37.init(width, height);
694 _codec47.init(width, height);
695 _alreadyInit = true;
696 }
697
698 _width = _vm->_screenWidth;
699 _height = _vm->_screenHeight;
700 b.getWord();
701 b.getWord();
702
703 int32 chunk_size = b.getSize() - 14;
704 byte *chunk_buffer = (byte *)malloc(chunk_size);
705 assert(chunk_buffer);
706 b.read(chunk_buffer, chunk_size);
707
708 switch (codec) {
709 case 1:
710 case 3:
711 smush_decode_codec1(_dst, chunk_buffer, left, top, height, width, _vm->_screenWidth);
712 break;
713 case 37:
714 _codec37.decode(_dst, chunk_buffer);
715 break;
716 case 47:
717 _codec47.decode(_dst, chunk_buffer);
718 break;
719 default:
720 error("Invalid codec for frame object : %d", (int)codec);
721 }
722
723 if (_storeFrame) {
724 if (_frameBuffer == NULL) {
725 _frameBuffer = (byte *)malloc(_width * _height);
726 }
727 memcpy(_frameBuffer, _dst, _width * _height);
728 _storeFrame = false;
729 }
730
731 free(chunk_buffer);
732}
733
734void SmushPlayer::handleFrame(Chunk &b) {
735 checkBlock(b, TYPE_FRME);
736 debug(6, "SmushPlayer::handleFrame(%d)", _frame);
737 _skipNext = false;
738
739 uint32 start_time, end_time;
740 start_time = _vm->_system->get_msecs();
741
742 if (_insanity) {
743 _vm->_insane->procPreRendering();
744 }
745
746 while (!b.eof()) {
747 Chunk *sub = b.subBlock();
748 if (sub->getSize() & 1) b.seek(1);
749 switch (sub->getType()) {
750 case TYPE_NPAL:
751 handleNewPalette(*sub);
752 break;
753 case TYPE_FOBJ:
754 handleFrameObject(*sub);
755 break;
756 case TYPE_PSAD:
757 handleSoundFrame(*sub);
758 break;
759 case TYPE_TRES:
760 handleTextResource(*sub);
761 break;
762 case TYPE_XPAL:
763 handleDeltaPalette(*sub);
764 break;
765 case TYPE_IACT:
766 // FIXME: check parameters
767 if (_insanity)
768 _vm->_insane->procIACT(_dst, 0, 0, 0, *sub, 0, 0);
769 else
770 handleIACT(*sub);
771 break;
772 case TYPE_STOR:
773 handleStore(*sub);
774 break;
775 case TYPE_FTCH:
776 handleFetch(*sub);
777 break;
778 case TYPE_SKIP:
779 if (_insanity)
780 _vm->_insane->procSKIP(*sub);
781 else
782 handleSkip(*sub);
783 break;
784 case TYPE_TEXT:
785 handleTextResource(*sub);
786 break;
787 default:
788 error("Unknown frame subChunk found : %s, %d", Chunk::ChunkString(sub->getType()), sub->getSize());
789 }
790 delete sub;
791 }
792
793 if (_insanity) {
794 _vm->_insane->procPostRendering(_dst, 0, 0, 0, _frame, _nbframes-1);
795 }
796
797 end_time = _vm->_system->get_msecs();
798
799 updateScreen();
800 _smixer->handleFrame();
801
802 debug(5, "Smush stats: FRME( %03d ), Limit(%d)", end_time - start_time, _speed / 1000);
803
804 _frame++;
805}
806
807void SmushPlayer::handleAnimHeader(Chunk &b) {
808 checkBlock(b, TYPE_AHDR, 0x300 + 6);
809 debug(6, "SmushPlayer::handleAnimHeader()");
810
811 _version = b.getWord();
812 _nbframes = b.getWord();
813 b.getWord();
814 if (!_skipPalette) {
815 readPalette(_pal, b);
816 setPalette(_pal);
817 }
818}
819
820void SmushPlayer::setupAnim(const char *file, const char *directory) {
821 Chunk *sub;
822 int i;
823 char file_font[11];
824
825 _base = new FileChunk(file, directory);
826 sub = _base->subBlock();
827 checkBlock(*sub, TYPE_AHDR);
828 handleAnimHeader(*sub);
829
830 if (_insanity) {
831 if (!((_vm->_features & GF_DEMO) && (_vm->_features & GF_PC)))
832 readString("mineroad.trs", directory);
833 } else
834 readString(file, directory);
835
836 if (_vm->_gameId == GID_FT) {
837 if (!((_vm->_features & GF_DEMO) && (_vm->_features & GF_PC))) {
838 _sf[0] = new SmushFont(true, false);
839 _sf[1] = new SmushFont(true, false);
840 _sf[2] = new SmushFont(true, false);
841 _sf[3] = new SmushFont(true, false);
842 _sf[0]->loadFont("scummfnt.nut", directory);
843 _sf[1]->loadFont("techfnt.nut", directory);
844 _sf[2]->loadFont("titlfnt.nut", directory);
845 _sf[3]->loadFont("specfnt.nut", directory);
846 }
847 } else if (_vm->_gameId == GID_DIG) {
848 if (!(_vm->_features & GF_DEMO)) {
849 for (i = 0; i < 4; i++) {
850 sprintf(file_font, "font%d.nut", i);
851 _sf[i] = new SmushFont(i != 0, false);
852 _sf[i]->loadFont(file_font, directory);
853 }
854 }
855 } else if (_vm->_gameId == GID_CMI) {
856 for (i = 0; i < 5; i++) {
857 if ((_vm->_features & GF_DEMO) && (i == 4))
858 break;
859 sprintf(file_font, "font%d.nut", i);
860 _sf[i] = new SmushFont(false, true);
861 _sf[i]->loadFont(file_font, directory);
862 }
863 } else {
864 error("SmushPlayer::setupAnim() Unknown font setup for game");
865 }
866
867 delete sub;
868}
869
870void SmushPlayer::parseNextFrame() {
871 if (_vm->_smushPaused)
872 return;
873
874 if (_base->eof()) {
875 _vm->_videoFinished = true;
876 return;
877 }
878
879 Chunk *sub = _base->subBlock();
880
881 switch (sub->getType()) {
882 case TYPE_FRME:
883 handleFrame(*sub);
884 break;
885 case TYPE_AHDR: // FT INSANE may seek file to the beginning
886 handleAnimHeader(*sub);
887 break;
888 default:
889 error("Unknown Chunk found at %x: %x, %d", _base->tell(), sub->getType(), sub->getSize());
890 }
891 delete sub;
892}
893
894void SmushPlayer::setPalette(const byte *palette) {
895 byte palette_colors[1024];
896 byte *p = palette_colors;
897
898 for (int i = 0; i != 256; ++i) {
899 *p++ = _pal[i * 3 + 0] = *palette++; // red
900 *p++ = _pal[i * 3 + 1] = *palette++; // green
901 *p++ = _pal[i * 3 + 2] = *palette++; // blue
902 *p++ = 0;
903 }
904
905 _vm->_system->set_palette(palette_colors, 0, 256);
906}
907
908void SmushPlayer::setPaletteValue(int n, byte r, byte g, byte b) {
909 _pal[n * 3 + 0] = r;
910 _pal[n * 3 + 1] = g;
911 _pal[n * 3 + 2] = b;
912
913 _vm->_system->set_palette(_pal, n, 1);
914}
915
916void SmushPlayer::updateScreen() {
917#ifdef DUMP_SMUSH_FRAMES
918 char fileName[100];
919 // change path below for dump png files
920 sprintf(fileName, "/path/to/somethere/%s%04d.png", _vm->getGameName(), _frame);
921 FILE *file = fopen(fileName, "wb");
922 if (file == NULL)
923 error("can't open file for writing png");
924
925 png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
926 if (png_ptr == NULL) {
927 fclose(file);
928 error("can't write png header");
929 }
930 png_infop info_ptr = png_create_info_struct(png_ptr);
931 if (info_ptr == NULL) {
932 fclose(file);
933 error("can't create png info struct");
934 }
935 if (setjmp(png_ptr->jmpbuf)) {
936 fclose(file);
937 error("png jmpbuf error");
938 }
939
940 png_init_io(png_ptr, file);
941
942 png_set_IHDR(png_ptr, info_ptr, _width, _height, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
943 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
944
945 png_colorp palette = (png_colorp)png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof (png_color));
946 for (int i = 0; i != 256; ++i) {
947 (palette + i)->red = _pal[i * 3 + 0];
948 (palette + i)->green = _pal[i * 3 + 1];
949 (palette + i)->blue = _pal[i * 3 + 2];
950 }
951
952 png_set_PLTE(png_ptr, info_ptr, palette, PNG_MAX_PALETTE_LENGTH);
953
954 png_write_info(png_ptr, info_ptr);
955 png_set_flush(png_ptr, 10);
956
957 png_bytep row_pointers[480];
958 for (int y = 0 ; y < _height ; y++)
959 row_pointers[y] = (png_byte *) (_dst + y * _width);
960 png_write_image(png_ptr, row_pointers);
961 png_write_end(png_ptr, info_ptr);
962 png_free(png_ptr, palette);
963
964 fclose(file);
965 png_destroy_write_struct(&png_ptr, &info_ptr);
966#endif
967
968 uint32 end_time, start_time = _vm->_system->get_msecs();
969 _vm->_system->copy_rect(_dst, _width, 0, 0, _width, _height);
970 _updateNeeded = true;
971 end_time = _vm->_system->get_msecs();
972 debug(4, "Smush stats: updateScreen( %03d )", end_time - start_time);
973}
974
975void SmushPlayer::insanity(bool flag) {
976 _insanity = flag;
977}
978
979void SmushPlayer::seekSan(const char *file, const char *directory, int32 pos, int32 contFrame) {
980 if(_smixer)
981 _smixer->stop();
982
983 if (file) {
984 if (_base) {
985 _base->seek(0, FileChunk::seek_end);
986 delete _base;
987 }
988
989 _base = new FileChunk(file, directory);
990 // In this case we need to get palette and number of frames
991 if (pos > 8) {
992 Chunk *sub = _base->subBlock();
993 checkBlock(*sub, TYPE_AHDR);
994 handleAnimHeader(*sub);
995 delete sub;
996 }
997 if (pos >= 8)
998 pos -= 8;
999
1000 _skipPalette = false;
1001 } else {
1002 _base->reinit(pos);
1003 _skipPalette = true;
1004 }
1005
1006 if (pos != 8 && pos) {
1007 _middleAudio = true;
1008 }
1009
1010 _base->seek(pos, FileChunk::seek_start);
1011
1012 _frame = contFrame;
1013}
1014
1015void SmushPlayer::play(const char *filename, const char *directory, int32 offset, int32 startFrame) {
1016
1017 // Verify the specified file exists
1018 File f;
1019 int i;
1020 f.open(filename, directory);
1021 if (!f.isOpen()) {
1022 warning("SmushPlayer::play() File not found %s", filename);
1023 return;
1024 }
1025 f.close();
1026
1027 _updateNeeded = false;
1028
1029 // Hide mouse
1030 bool oldMouseState = _vm->_system->show_mouse(false);
1031
1032 // Load the video
1033 setupAnim(filename, directory);
1034 init();
1035
1036 i = 0;
1037
1038 if (offset) {
1039 _base->seek(offset - 8, FileChunk::seek_start);
1040 _frame = startFrame;
1041 _middleAudio = true;
1042 }
1043
1044 while (true) {
1045 if (i == 2) _vm->_system->warp_mouse(0,0);
1046 if (i == 4) {
1047 _vm->_system->warp_mouse(0,1);
1048 i = 0; }
1049 i++;
1050 _vm->parseEvents();
1051 _vm->processKbd(true);
1052 if (_updateNeeded) {
1053
1054 uint32 end_time, start_time;
1055
1056 start_time = _vm->_system->get_msecs();
1057 _vm->_system->update_screen();
1058 _updateNeeded = false;
1059
1060 if (_insanity)
1061 _vm->_sound->processSoundQues();
1062
1063 end_time = _vm->_system->get_msecs();
1064
1065 debug(4, "Smush stats: BackendUpdateScreen( %03d )", end_time - start_time);
1066
1067 }
1068 if (_vm->_videoFinished || _vm->_quit || _vm->_saveLoadFlag)
1069 break;
1070 _vm->_system->delay_msecs(10);
1071 };
1072
1073 deinit();
1074
1075 // Reset mouse state
1076 _vm->_system->show_mouse(oldMouseState);
1077}
1078
1079} // End of namespace Scumm