aboutsummaryrefslogtreecommitdiff
path: root/engines/scumm/smush/smush_player.cpp
diff options
context:
space:
mode:
authorMax Horn2006-02-11 22:45:04 +0000
committerMax Horn2006-02-11 22:45:04 +0000
commit26ee630756ebdd7c96bccede0881a8c8b98e8f2b (patch)
tree26e378d5cf990a2b81c2c96e9e683a7f333b62e8 /engines/scumm/smush/smush_player.cpp
parent2a9a0d4211b1ea5723f1409d91cb95de8984429e (diff)
downloadscummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.tar.gz
scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.tar.bz2
scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.zip
Moved engines to the new engines/ directory
svn-id: r20582
Diffstat (limited to 'engines/scumm/smush/smush_player.cpp')
-rw-r--r--engines/scumm/smush/smush_player.cpp1359
1 files changed, 1359 insertions, 0 deletions
diff --git a/engines/scumm/smush/smush_player.cpp b/engines/scumm/smush/smush_player.cpp
new file mode 100644
index 0000000000..926e7f9f87
--- /dev/null
+++ b/engines/scumm/smush/smush_player.cpp
@@ -0,0 +1,1359 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2002-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/stdafx.h"
+
+#include "base/engine.h"
+
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/system.h"
+#include "common/timer.h"
+#include "common/util.h"
+
+#include "scumm/bomp.h"
+#include "scumm/imuse_digi/dimuse.h"
+#include "scumm/imuse.h"
+#include "scumm/scumm.h"
+#include "scumm/sound.h"
+#include "scumm/smush/channel.h"
+#include "scumm/smush/chunk_type.h"
+#include "scumm/smush/chunk.h"
+#include "scumm/smush/smush_font.h"
+#include "scumm/smush/smush_mixer.h"
+#include "scumm/smush/smush_player.h"
+
+#include "scumm/insane/insane.h"
+
+#include "sound/mixer.h"
+#include "sound/vorbis.h"
+#include "sound/mp3.h"
+
+#ifdef DUMP_SMUSH_FRAMES
+#include <png.h>
+#endif
+
+#ifdef USE_ZLIB
+#include <zlib.h>
+#endif
+
+namespace Scumm {
+
+const int MAX_STRINGS = 200;
+
+class StringResource {
+private:
+
+ struct {
+ int id;
+ char *string;
+ } _strings[MAX_STRINGS];
+
+ int _nbStrings;
+ int _lastId;
+ const char *_lastString;
+
+public:
+
+ StringResource() :
+ _nbStrings(0),
+ _lastId(-1) {
+ };
+ ~StringResource() {
+ for (int32 i = 0; i < _nbStrings; i++) {
+ delete []_strings[i].string;
+ }
+ }
+
+ bool init(char *buffer, int32 length) {
+ char *def_start = strchr(buffer, '#');
+ while (def_start != NULL) {
+ char *def_end = strchr(def_start, '\n');
+ assert(def_end != NULL);
+
+ char *id_end = def_end;
+ while (id_end >= def_start && !isdigit(*(id_end-1))) {
+ id_end--;
+ }
+
+ assert(id_end > def_start);
+ char *id_start = id_end;
+ while (isdigit(*(id_start - 1))) {
+ id_start--;
+ }
+
+ char idstring[32];
+ memcpy(idstring, id_start, id_end - id_start);
+ idstring[id_end - id_start] = 0;
+ int32 id = atoi(idstring);
+ char *data_start = def_end;
+
+ while (*data_start == '\n' || *data_start == '\r') {
+ data_start++;
+ }
+ char *data_end = data_start;
+
+ while (1) {
+ if (data_end[-2] == '\r' && data_end[-1] == '\n' && data_end[0] == '\r' && data_end[1] == '\n') {
+ break;
+ }
+ // In Russian Full Throttle strings are finished with
+ // just one pair of CR-LF
+ if (data_end[-2] == '\r' && data_end[-1] == '\n' && data_end[0] == '#') {
+ break;
+ }
+ data_end++;
+ if (data_end >= buffer + length) {
+ data_end = buffer + length;
+ break;
+ }
+ }
+
+ data_end -= 2;
+ assert(data_end > data_start);
+ char *value = new char[data_end - data_start + 1];
+ assert(value);
+ memcpy(value, data_start, data_end - data_start);
+ value[data_end - data_start] = 0;
+ char *line_start = value;
+ char *line_end;
+
+ while ((line_end = strchr(line_start, '\n'))) {
+ line_start = line_end+1;
+ if (line_start[0] == '/' && line_start[1] == '/') {
+ line_start += 2;
+ if (line_end[-1] == '\r')
+ line_end[-1] = ' ';
+ else
+ *line_end++ = ' ';
+ memmove(line_end, line_start, strlen(line_start)+1);
+ }
+ }
+ _strings[_nbStrings].id = id;
+ _strings[_nbStrings].string = value;
+ _nbStrings ++;
+ def_start = strchr(data_end + 2, '#');
+ }
+ return true;
+ }
+
+ const char *get(int id) {
+ if (id == _lastId) {
+ return _lastString;
+ }
+ debugC(DEBUG_SMUSH, "StringResource::get(%d)", id);
+ for (int i = 0; i < _nbStrings; i++) {
+ if (_strings[i].id == id) {
+ _lastId = id;
+ _lastString = _strings[i].string;
+ return _strings[i].string;
+ }
+ }
+ warning("invalid string id : %d", id);
+ _lastId = -1;
+ _lastString = "unknown string";
+ return _lastString;
+ }
+};
+
+static StringResource *getStrings(ScummEngine *vm, const char *file, bool is_encoded) {
+ debugC(DEBUG_SMUSH, "trying to read text ressources from %s", file);
+ ScummFile theFile;
+
+ vm->openFile(theFile, file);
+ if (!theFile.isOpen()) {
+ return 0;
+ }
+ int32 length = theFile.size();
+ char *filebuffer = new char [length + 1];
+ assert(filebuffer);
+ theFile.read(filebuffer, length);
+ filebuffer[length] = 0;
+
+ if (is_encoded) {
+ enum {
+ ETRS_HEADER_LENGTH = 16
+ };
+ assert(length > ETRS_HEADER_LENGTH);
+ Chunk::type type = READ_BE_UINT32(filebuffer);
+
+ if (type != TYPE_ETRS) {
+ delete [] filebuffer;
+ return getStrings(vm, file, false);
+ }
+
+ char *old = filebuffer;
+ filebuffer = new char[length - ETRS_HEADER_LENGTH + 1];
+ for (int32 i = ETRS_HEADER_LENGTH; i < length; i++) {
+ filebuffer[i - ETRS_HEADER_LENGTH] = old[i] ^ 0xCC;
+ }
+ filebuffer[length - ETRS_HEADER_LENGTH] = '\0';
+ delete []old;
+ length -= ETRS_HEADER_LENGTH;
+ }
+ StringResource *sr = new StringResource;
+ assert(sr);
+ sr->init(filebuffer, length);
+ delete []filebuffer;
+ return sr;
+}
+
+void SmushPlayer::timerCallback(void *refCon) {
+ ((SmushPlayer *)refCon)->parseNextFrame();
+#ifdef _WIN32_WCE
+ ((SmushPlayer *)refCon)->_inTimer = true;
+ ((SmushPlayer *)refCon)->_inTimerCount++;
+#endif
+}
+
+SmushPlayer::SmushPlayer(ScummEngine_v6 *scumm, int speed) {
+ _vm = scumm;
+ _version = -1;
+ _nbframes = 0;
+ _smixer = 0;
+ _strings = NULL;
+ _sf[0] = NULL;
+ _sf[1] = NULL;
+ _sf[2] = NULL;
+ _sf[3] = NULL;
+ _sf[4] = NULL;
+ _base = NULL;
+ _frameBuffer = NULL;
+ _specialBuffer = NULL;
+
+ _seekPos = -1;
+
+ _skipNext = false;
+ _subtitles = ConfMan.getBool("subtitles");
+ _dst = NULL;
+ _storeFrame = false;
+ _compressedFileMode = false;
+ _width = 0;
+ _height = 0;
+ _IACTpos = 0;
+ _soundFrequency = 22050;
+ _initDone = false;
+ _speed = speed;
+ _insanity = false;
+ _middleAudio = false;
+ _skipPalette = false;
+ _IACTstream = NULL;
+#ifdef _WIN32_WCE
+ _inTimer = false;
+ _inTimerCount = 0;
+ _inTimerCountRedraw = ConfMan.getInt("Smush_force_redraw");
+#endif
+}
+
+SmushPlayer::~SmushPlayer() {
+ release();
+}
+
+void SmushPlayer::init() {
+ _frame = 0;
+ _alreadyInit = false;
+ _vm->_smushVideoShouldFinish = false;
+ _vm->setDirtyColors(0, 255);
+ _dst = _vm->virtscr[0].getPixels(0, 0);
+
+ // HACK HACK HACK: This is an *evil* trick, beware!
+ // We do this to fix bug #1037052. A proper solution would change all the
+ // drawing code to use the pitch value specified by the virtual screen.
+ // However, since a lot of the SMUSH code currently assumes the screen
+ // width and pitch to be equal, this will require lots of changes. So
+ // we resort to this hackish solution for now.
+ _origPitch = _vm->virtscr[0].pitch;
+ _origNumStrips = _vm->gdi._numStrips;
+ _vm->virtscr[0].pitch = _vm->virtscr[0].w;
+ _vm->gdi._numStrips = _vm->virtscr[0].w / 8;
+
+ _smixer = new SmushMixer(_vm->_mixer);
+ Common::g_timer->installTimerProc(&timerCallback, 1000000 / _speed, this);
+
+ _initDone = true;
+}
+
+void SmushPlayer::release() {
+ if (!_initDone)
+ return;
+
+ _vm->_timer->removeTimerProc(&timerCallback);
+
+ _vm->_smushVideoShouldFinish = true;
+
+ for (int i = 0; i < 5; i++) {
+ delete _sf[i];
+ _sf[i] = NULL;
+ }
+
+ delete _strings;
+ _strings = NULL;
+
+ if (_smixer)
+ _smixer->stop();
+
+ delete _smixer;
+ _smixer = NULL;
+
+ delete _base;
+ _base = NULL;
+
+ free(_specialBuffer);
+ _specialBuffer = NULL;
+
+ free(_frameBuffer);
+ _frameBuffer = NULL;
+
+ _vm->_mixer->stopHandle(_compressedFileSoundHandle);
+
+ _vm->_mixer->stopHandle(_IACTchannel);
+ _IACTstream = 0;
+
+ _vm->_fullRedraw = true;
+
+ // WORKAROUND bug #1035739: This is hack to workaround some ugly palette
+ // issues, see the mentioned bug report for details.
+ _vm->_doEffect = false;
+
+
+ // HACK HACK HACK: This is an *evil* trick, beware! See above for
+ // some explanation.
+ _vm->virtscr[0].pitch = _origPitch;
+ _vm->gdi._numStrips = _origNumStrips;
+
+
+ _initDone = false;
+}
+
+void SmushPlayer::checkBlock(const Chunk &b, Chunk::type type_expected, uint32 min_size) {
+ if (type_expected != b.getType()) {
+ error("Chunk type is different from expected : %x != %x", b.getType(), type_expected);
+ }
+ if (min_size > b.getSize()) {
+ error("Chunk size is inferior than minimum required size : %d < %d", b.getSize(), min_size);
+ }
+}
+
+void SmushPlayer::handleSoundBuffer(int32 track_id, int32 index, int32 max_frames, int32 flags, int32 vol, int32 pan, Chunk &b, int32 size) {
+ debugC(DEBUG_SMUSH, "SmushPlayer::handleSoundBuffer(%d, %d)", track_id, index);
+// if ((flags & 128) == 128) {
+// return;
+// }
+// if ((flags & 64) == 64) {
+// return;
+// }
+ SmushChannel *c = _smixer->findChannel(track_id);
+ if (c == NULL) {
+ c = new SaudChannel(track_id, _soundFrequency);
+ _smixer->addChannel(c);
+ }
+
+ if (_middleAudio || (index == 0)) {
+ c->setParameters(max_frames, flags, vol, pan, index);
+ } else {
+ c->checkParameters(index, max_frames, flags, vol, pan);
+ }
+ _middleAudio = false;
+ c->appendData(b, size);
+}
+
+void SmushPlayer::handleSoundFrame(Chunk &b) {
+ checkBlock(b, TYPE_PSAD);
+ debugC(DEBUG_SMUSH, "SmushPlayer::handleSoundFrame()");
+
+ int32 track_id = b.getWord();
+ int32 index = b.getWord();
+ int32 max_frames = b.getWord();
+ int32 flags = b.getWord();
+ int32 vol = b.getByte();
+ int32 pan = b.getChar();
+ if (index == 0) {
+ debugC(DEBUG_SMUSH, "track_id:%d, max_frames:%d, flags:%d, vol:%d, pan:%d", track_id, max_frames, flags, vol, pan);
+ }
+ int32 size = b.getSize() - 10;
+ handleSoundBuffer(track_id, index, max_frames, flags, vol, pan, b, size);
+}
+
+void SmushPlayer::handleSkip(Chunk &b) {
+ checkBlock(b, TYPE_SKIP, 4);
+ int32 code = b.getDword();
+ debugC(DEBUG_SMUSH, "SmushPlayer::handleSkip(%d)", code);
+ if (code >= 0 && code < 37)
+ _skipNext = _skips[code];
+ else
+ _skipNext = true;
+}
+
+void SmushPlayer::handleStore(Chunk &b) {
+ debugC(DEBUG_SMUSH, "SmushPlayer::handleStore()");
+ checkBlock(b, TYPE_STOR, 4);
+ _storeFrame = true;
+}
+
+void SmushPlayer::handleFetch(Chunk &b) {
+ debugC(DEBUG_SMUSH, "SmushPlayer::handleFetch()");
+ checkBlock(b, TYPE_FTCH, 6);
+
+ if (_frameBuffer != NULL) {
+ memcpy(_dst, _frameBuffer, _width * _height);
+ }
+}
+
+void SmushPlayer::handleIACT(Chunk &b) {
+ checkBlock(b, TYPE_IACT, 8);
+ debugC(DEBUG_SMUSH, "SmushPlayer::handleImuseAction()");
+
+ /* int code = */ b.getWord();
+ int flags = b.getWord();
+ int unknown = b.getShort();
+ int track_flags = b.getWord();
+
+ assert(flags == 46 && unknown == 0);
+ int track_id = b.getWord();
+ int index = b.getWord();
+ int nbframes = b.getWord();
+ int32 size = b.getDword();
+ int32 bsize = b.getSize() - 18;
+
+ if (_vm->_gameId != GID_CMI) {
+ int32 track = track_id;
+ if (track_flags == 1) {
+ track = track_id + 100;
+ } else if (track_flags == 2) {
+ track = track_id + 200;
+ } else if (track_flags == 3) {
+ track = track_id + 300;
+ } else if ((track_flags >= 100) && (track_flags <= 163)) {
+ track = track_id + 400;
+ } else if ((track_flags >= 200) && (track_flags <= 263)) {
+ track = track_id + 500;
+ } else if ((track_flags >= 300) && (track_flags <= 363)) {
+ track = track_id + 600;
+ } else {
+ error("ImuseChannel::handleIACT(): bad track_flags: %d", track_flags);
+ }
+ debugC(DEBUG_SMUSH, "SmushPlayer::handleIACT(): %d, %d, %d", track, index, track_flags);
+
+ SmushChannel *c = _smixer->findChannel(track);
+ if (c == 0) {
+ c = new ImuseChannel(track, _soundFrequency);
+ _smixer->addChannel(c);
+ }
+ if (index == 0)
+ c->setParameters(nbframes, size, track_flags, unknown, 0);
+ else
+ c->checkParameters(index, nbframes, size, track_flags, unknown);
+ c->appendData(b, bsize);
+ } else {
+ byte output_data[4096];
+ byte *src = (byte *)malloc(bsize);
+ b.read(src, bsize);
+ byte *d_src = src;
+ byte value;
+
+ while (bsize > 0) {
+ if (_IACTpos >= 2) {
+ int32 len = READ_BE_UINT16(_IACToutput) + 2;
+ len -= _IACTpos;
+ if (len > bsize) {
+ memcpy(_IACToutput + _IACTpos, d_src, bsize);
+ _IACTpos += bsize;
+ bsize = 0;
+ } else {
+ memcpy(_IACToutput + _IACTpos, d_src, len);
+ byte *dst = output_data;
+ byte *d_src2 = _IACToutput;
+ d_src2 += 2;
+ int32 count = 1024;
+ byte variable1 = *d_src2++;
+ byte variable2 = variable1 / 16;
+ variable1 &= 0x0f;
+ do {
+ value = *(d_src2++);
+ if (value == 0x80) {
+ *dst++ = *d_src2++;
+ *dst++ = *d_src2++;
+ } else {
+ int16 val = (int8)value << variable2;
+ *dst++ = val >> 8;
+ *dst++ = (byte)(val);
+ }
+ value = *(d_src2++);
+ if (value == 0x80) {
+ *dst++ = *d_src2++;
+ *dst++ = *d_src2++;
+ } else {
+ int16 val = (int8)value << variable1;
+ *dst++ = val >> 8;
+ *dst++ = (byte)(val);
+ }
+ } while (--count);
+
+ if (!_IACTstream) {
+ _IACTstream = makeAppendableAudioStream(22050, Audio::Mixer::FLAG_STEREO | Audio::Mixer::FLAG_16BITS, 400000);
+ _vm->_mixer->playInputStream(Audio::Mixer::kSFXSoundType, &_IACTchannel, _IACTstream);
+ }
+ _IACTstream->append(output_data, 0x1000);
+
+ bsize -= len;
+ d_src += len;
+ _IACTpos = 0;
+ }
+ } else {
+ if (bsize > 1 && _IACTpos == 0) {
+ *(_IACToutput + 0) = *d_src++;
+ _IACTpos = 1;
+ bsize--;
+ }
+ *(_IACToutput + _IACTpos) = *d_src++;
+ _IACTpos++;
+ bsize--;
+ }
+ }
+
+ free(src);
+ }
+}
+
+void SmushPlayer::handleTextResource(Chunk &b) {
+ int pos_x = b.getShort();
+ int pos_y = b.getShort();
+ int flags = b.getShort();
+ int left = b.getShort();
+ int top = b.getShort();
+ int right = b.getShort();
+ /*int32 height =*/ b.getShort();
+ /*int32 unk2 =*/ b.getWord();
+
+ const char *str;
+ char *string = NULL, *string2 = NULL;
+ if (b.getType() == TYPE_TEXT) {
+ string = (char *)malloc(b.getSize() - 16);
+ str = string;
+ b.read(string, b.getSize() - 16);
+ } else {
+ int string_id = b.getWord();
+ if (!_strings)
+ return;
+ str = _strings->get(string_id);
+ }
+
+ // if subtitles disabled and bit 3 is set, then do not draw
+ if ((!_subtitles) && ((flags & 8) == 8))
+ return;
+
+ SmushFont *sf = _sf[0];
+ int color = 15;
+ while (*str == '/') {
+ str++; // For Full Throttle text resources
+ }
+
+ byte transBuf[512];
+ if (_vm->_gameId == GID_CMI) {
+ _vm->translateText((const byte *)str - 1, transBuf);
+ while (*str++ != '/')
+ ;
+ string2 = (char *)transBuf;
+
+ // If string2 contains formatting information there probably
+ // wasn't any translation for it in the language.tab file. In
+ // that case, pretend there is no string2.
+ if (string2[0] == '^')
+ string2[0] = 0;
+ }
+
+ while (str[0] == '^') {
+ switch (str[1]) {
+ case 'f':
+ {
+ int id = str[3] - '0';
+ str += 4;
+ sf = _sf[id];
+ }
+ break;
+ case 'c':
+ {
+ color = str[4] - '0' + 10 *(str[3] - '0');
+ str += 5;
+ }
+ break;
+ default:
+ error("invalid escape code in text string");
+ }
+ }
+
+ // HACK. This is to prevent bug #1310846. In updated Win95 dig
+ // there is such line:
+ //
+ // ^f01^c001LEAD TESTER
+ // Chris Purvis
+ // ^f01
+ // ^f01^c001WINDOWS COMPATIBILITY
+ // Chip Hinnenberg
+ // ^f01^c001WINDOWS TESTING
+ // Jim Davison
+ // Lynn Selk
+ //
+ // i.e. formatting exists not in the first line only
+ // We just strip that off and assume that neither font
+ // nor font color was altered. Proper fix would be to feed
+ // drawString() with each line sequentally
+ char *string3 = NULL, *sptr2;
+ const char *sptr;
+
+ if (strchr(str, '^')) {
+ string3 = (char *)malloc(strlen(str) + 1);
+
+ for (sptr = str, sptr2 = string3; *sptr;) {
+ if (*sptr == '^') {
+ switch (sptr[1]) {
+ case 'f':
+ sptr += 4;
+ break;
+ case 'c':
+ sptr += 5;
+ break;
+ default:
+ error("invalid escape code in text string");
+ }
+ } else {
+ *sptr2++ = *sptr++;
+ }
+ }
+ *sptr2++ = *sptr++; // copy zero character
+ str = string3;
+ }
+
+ assert(sf != NULL);
+ sf->setColor(color);
+
+ if (_vm->_gameId == GID_CMI && string2[0] != 0) {
+ str = string2;
+ }
+
+ // flags:
+ // bit 0 - center 1
+ // bit 1 - not used 2
+ // bit 2 - ??? 4
+ // bit 3 - wrap around 8
+ switch (flags & 9) {
+ case 0:
+ sf->drawString(str, _dst, _width, _height, pos_x, pos_y, false);
+ break;
+ case 1:
+ sf->drawString(str, _dst, _width, _height, pos_x, MAX(pos_y, top), true);
+ break;
+ case 8:
+ // FIXME: Is 'right' the maximum line width here, just
+ // as it is in the next case? It's used several times
+ // in The Dig's intro, where 'left' and 'right' are
+ // always 0 and 321 respectively, and apparently we
+ // handle that correctly.
+ sf->drawStringWrap(str, _dst, _width, _height, pos_x, MAX(pos_y, top), left, right, false);
+ break;
+ case 9:
+ // In this case, the 'right' parameter is actually the
+ // maximum line width. This explains why it's sometimes
+ // smaller than 'left'.
+ //
+ // Note that in The Dig's "Spacetime Six" movie it's
+ // 621. I have no idea what that means.
+ sf->drawStringWrap(str, _dst, _width, _height, pos_x, MAX(pos_y, top), left, MIN(left + right, _width), true);
+ break;
+ default:
+ error("SmushPlayer::handleTextResource. Not handled flags: %d", flags);
+ }
+
+ if (string != NULL) {
+ free (string);
+ }
+ if (string3 != NULL) {
+ free (string3);
+ }
+}
+
+const char *SmushPlayer::getString(int id) {
+ return _strings->get(id);
+}
+
+bool SmushPlayer::readString(const char *file) {
+ const char *i = strrchr(file, '.');
+ if (i == NULL) {
+ error("invalid filename : %s", file);
+ }
+ char fname[260];
+ memcpy(fname, file, i - file);
+ strcpy(fname + (i - file), ".trs");
+ if ((_strings = getStrings(_vm, fname, false)) != 0) {
+ return true;
+ }
+
+ if ((_strings = getStrings(_vm, "digtxt.trs", true)) != 0) {
+ return true;
+ }
+ return false;
+}
+
+void SmushPlayer::readPalette(byte *out, Chunk &in) {
+ in.read(out, 0x300);
+}
+
+static byte delta_color(byte org_color, int16 delta_color) {
+ int t = (org_color * 129 + delta_color) / 128;
+ if (t > 255)
+ t = 255;
+ if (t < 0)
+ t = 0;
+ return (byte)t;
+}
+
+void SmushPlayer::handleDeltaPalette(Chunk &b) {
+ checkBlock(b, TYPE_XPAL);
+ debugC(DEBUG_SMUSH, "SmushPlayer::handleDeltaPalette()");
+
+ if (b.getSize() == 0x300 * 3 + 4) {
+
+ b.getWord();
+ b.getWord();
+
+ for (int i = 0; i < 0x300; i++) {
+ _deltaPal[i] = b.getWord();
+ }
+ readPalette(_pal, b);
+ setDirtyColors(0, 255);
+ } else if (b.getSize() == 6) {
+
+ b.getWord();
+ b.getWord();
+ b.getWord();
+
+ for (int i = 0; i < 0x300; i++) {
+ _pal[i] = delta_color(_pal[i], _deltaPal[i]);
+ }
+ setDirtyColors(0, 255);
+ } else {
+ error("SmushPlayer::handleDeltaPalette() Wrong size for DeltaPalette");
+ }
+}
+
+void SmushPlayer::handleNewPalette(Chunk &b) {
+ checkBlock(b, TYPE_NPAL, 0x300);
+ debugC(DEBUG_SMUSH, "SmushPlayer::handleNewPalette()");
+
+ if (_skipPalette)
+ return;
+
+ readPalette(_pal, b);
+ setDirtyColors(0, 255);
+}
+
+void smush_decode_codec1(byte *dst, const byte *src, int left, int top, int width, int height, int pitch);
+
+#ifdef USE_ZLIB
+void SmushPlayer::handleZlibFrameObject(Chunk &b) {
+ if (_skipNext) {
+ _skipNext = false;
+ return;
+ }
+
+ int32 chunkSize = b.getSize();
+ byte *chunkBuffer = (byte *)malloc(chunkSize);
+ assert(chunkBuffer);
+ b.read(chunkBuffer, chunkSize);
+
+ unsigned long decompressedSize = READ_BE_UINT32(chunkBuffer);
+ byte *fobjBuffer = (byte *)malloc(decompressedSize);
+ int result = uncompress(fobjBuffer, &decompressedSize, chunkBuffer + 4, chunkSize - 4);
+ if (result != Z_OK)
+ error("SmushPlayer::handleZlibFrameObject() Zlib uncompress error");
+ free(chunkBuffer);
+
+ byte *ptr = fobjBuffer;
+ int codec = READ_LE_UINT16(ptr); ptr += 2;
+ int left = READ_LE_UINT16(ptr); ptr += 2;
+ int top = READ_LE_UINT16(ptr); ptr += 2;
+ int width = READ_LE_UINT16(ptr); ptr += 2;
+ int height = READ_LE_UINT16(ptr); ptr += 2;
+
+ if ((height == 242) && (width == 384)) {
+ if (_specialBuffer == 0)
+ _specialBuffer = (byte *)malloc(242 * 384);
+ _dst = _specialBuffer;
+ } else if ((height > _vm->_screenHeight) || (width > _vm->_screenWidth))
+ return;
+ // FT Insane uses smaller frames to draw overlays with moving objects
+ // Other .san files do have them as well but their purpose in unknown
+ // and often it causes memory overdraw. So just skip those frames
+ else if (!_insanity && ((height != _vm->_screenHeight) || (width != _vm->_screenWidth)))
+ return;
+
+ if (!_alreadyInit) {
+ _codec37.init(width, height);
+ _codec47.init(width, height);
+ _alreadyInit = true;
+ }
+
+ if ((height == 242) && (width == 384)) {
+ _width = width;
+ _height = height;
+ } else {
+ _width = _vm->_screenWidth;
+ _height = _vm->_screenHeight;
+ }
+
+ switch (codec) {
+ case 1:
+ case 3:
+ smush_decode_codec1(_dst, fobjBuffer + 14, left, top, width, height, _vm->_screenWidth);
+ break;
+ case 37:
+ _codec37.decode(_dst, fobjBuffer + 14);
+ break;
+ case 47:
+ _codec47.decode(_dst, fobjBuffer + 14);
+ break;
+ default:
+ error("Invalid codec for frame object : %d", (int)codec);
+ }
+
+ if (_storeFrame) {
+ if (_frameBuffer == NULL) {
+ _frameBuffer = (byte *)malloc(_width * _height);
+ }
+ memcpy(_frameBuffer, _dst, _width * _height);
+ _storeFrame = false;
+ }
+
+ free(fobjBuffer);
+}
+#endif
+
+void SmushPlayer::handleFrameObject(Chunk &b) {
+ checkBlock(b, TYPE_FOBJ, 14);
+ if (_skipNext) {
+ _skipNext = false;
+ return;
+ }
+
+ int codec = b.getWord();
+ int left = b.getWord();
+ int top = b.getWord();
+ int width = b.getWord();
+ int height = b.getWord();
+
+ if ((height == 242) && (width == 384)) {
+ if (_specialBuffer == 0)
+ _specialBuffer = (byte *)malloc(242 * 384);
+ _dst = _specialBuffer;
+ } else if ((height > _vm->_screenHeight) || (width > _vm->_screenWidth))
+ return;
+ // FT Insane uses smaller frames to draw overlays with moving objects
+ // Other .san files do have them as well but their purpose in unknown
+ // and often it causes memory overdraw. So just skip those frames
+ else if (!_insanity && ((height != _vm->_screenHeight) || (width != _vm->_screenWidth)))
+ return;
+
+ if (!_alreadyInit) {
+ _codec37.init(width, height);
+ _codec47.init(width, height);
+ _alreadyInit = true;
+ }
+
+ if ((height == 242) && (width == 384)) {
+ _width = width;
+ _height = height;
+ } else {
+ _width = _vm->_screenWidth;
+ _height = _vm->_screenHeight;
+ }
+
+ b.getWord();
+ b.getWord();
+
+ int32 chunk_size = b.getSize() - 14;
+ byte *chunk_buffer = (byte *)malloc(chunk_size);
+ assert(chunk_buffer);
+ b.read(chunk_buffer, chunk_size);
+
+ switch (codec) {
+ case 1:
+ case 3:
+ smush_decode_codec1(_dst, chunk_buffer, left, top, width, height, _vm->_screenWidth);
+ break;
+ case 37:
+ _codec37.decode(_dst, chunk_buffer);
+ break;
+ case 47:
+ _codec47.decode(_dst, chunk_buffer);
+ break;
+ default:
+ error("Invalid codec for frame object : %d", (int)codec);
+ }
+
+ if (_storeFrame) {
+ if (_frameBuffer == NULL) {
+ _frameBuffer = (byte *)malloc(_width * _height);
+ }
+ memcpy(_frameBuffer, _dst, _width * _height);
+ _storeFrame = false;
+ }
+
+ free(chunk_buffer);
+}
+
+void SmushPlayer::handleFrame(Chunk &b) {
+ checkBlock(b, TYPE_FRME);
+ debugC(DEBUG_SMUSH, "SmushPlayer::handleFrame(%d)", _frame);
+ _skipNext = false;
+
+ uint32 start_time, end_time;
+ start_time = _vm->_system->getMillis();
+
+ if (_insanity) {
+ _vm->_insane->procPreRendering();
+ }
+
+ while (!b.eof()) {
+ Chunk *sub = b.subBlock();
+ switch (sub->getType()) {
+ case TYPE_NPAL:
+ handleNewPalette(*sub);
+ break;
+ case TYPE_FOBJ:
+ handleFrameObject(*sub);
+ break;
+#ifdef USE_ZLIB
+ case TYPE_ZFOB:
+ handleZlibFrameObject(*sub);
+ break;
+#endif
+ case TYPE_PSAD:
+ if (!_compressedFileMode)
+ handleSoundFrame(*sub);
+ break;
+ case TYPE_TRES:
+ handleTextResource(*sub);
+ break;
+ case TYPE_XPAL:
+ handleDeltaPalette(*sub);
+ break;
+ case TYPE_IACT:
+ // FIXME: check parameters
+ if (_insanity)
+ _vm->_insane->procIACT(_dst, 0, 0, 0, *sub, 0, 0);
+ else {
+ if (!_compressedFileMode)
+ handleIACT(*sub);
+ }
+ break;
+ case TYPE_STOR:
+ handleStore(*sub);
+ break;
+ case TYPE_FTCH:
+ handleFetch(*sub);
+ break;
+ case TYPE_SKIP:
+ if (_insanity)
+ _vm->_insane->procSKIP(*sub);
+ else
+ handleSkip(*sub);
+ break;
+ case TYPE_TEXT:
+ handleTextResource(*sub);
+ break;
+ default:
+ error("Unknown frame subChunk found : %s, %d", Chunk::ChunkString(sub->getType()), sub->getSize());
+ }
+
+ b.reseek();
+ if (sub->getSize() & 1)
+ b.seek(1);
+
+ delete sub;
+ }
+
+ if (_insanity) {
+ _vm->_insane->procPostRendering(_dst, 0, 0, 0, _frame, _nbframes-1);
+ }
+
+ end_time = _vm->_system->getMillis();
+
+ if (_width != 0 && _height != 0) {
+#ifdef _WIN32_WCE
+ if (!_inTimer || _inTimerCount == _inTimerCountRedraw) {
+ updateScreen();
+ _inTimerCount = 0;
+ }
+#else
+ updateScreen();
+#endif
+ }
+ _smixer->handleFrame();
+
+ debugC(DEBUG_SMUSH, "Smush stats: FRME( %03d ), Limit(%d)", end_time - start_time, _speed);
+
+ _frame++;
+}
+
+void SmushPlayer::handleAnimHeader(Chunk &b) {
+ checkBlock(b, TYPE_AHDR, 0x300 + 6);
+ debugC(DEBUG_SMUSH, "SmushPlayer::handleAnimHeader()");
+
+ _version = b.getWord();
+ _nbframes = b.getWord();
+ b.getWord();
+
+ if (_skipPalette)
+ return;
+
+ readPalette(_pal, b);
+ setDirtyColors(0, 255);
+}
+
+void SmushPlayer::setupAnim(const char *file) {
+ int i;
+ char file_font[11];
+
+ if (_insanity) {
+ if (!((_vm->_features & GF_DEMO) && (_vm->_platform == Common::kPlatformPC)))
+ readString("mineroad.trs");
+ } else
+ readString(file);
+
+ if (_vm->_gameId == GID_FT) {
+ if (!((_vm->_features & GF_DEMO) && (_vm->_platform == Common::kPlatformPC))) {
+ _sf[0] = new SmushFont(_vm, true, false);
+ _sf[1] = new SmushFont(_vm, true, false);
+ _sf[2] = new SmushFont(_vm, true, false);
+ _sf[3] = new SmushFont(_vm, true, false);
+ _sf[0]->loadFont("scummfnt.nut");
+ _sf[1]->loadFont("techfnt.nut");
+ _sf[2]->loadFont("titlfnt.nut");
+ _sf[3]->loadFont("specfnt.nut");
+ }
+ } else if (_vm->_gameId == GID_DIG) {
+ if (!(_vm->_features & GF_DEMO)) {
+ for (i = 0; i < 4; i++) {
+ sprintf(file_font, "font%d.nut", i);
+ _sf[i] = new SmushFont(_vm, i != 0, false);
+ _sf[i]->loadFont(file_font);
+ }
+ }
+ } else if (_vm->_gameId == GID_CMI) {
+ for (i = 0; i < 5; i++) {
+ if ((_vm->_features & GF_DEMO) && (i == 4))
+ break;
+ sprintf(file_font, "font%d.nut", i);
+ _sf[i] = new SmushFont(_vm, false, true);
+ _sf[i]->loadFont(file_font);
+ }
+ } else {
+ error("SmushPlayer::setupAnim() Unknown font setup for game");
+ }
+}
+
+void SmushPlayer::parseNextFrame() {
+ Common::StackLock lock(_mutex);
+
+ Chunk *sub;
+
+ if (_vm->_smushPaused)
+ return;
+
+ if (_seekPos >= 0) {
+ if (_smixer)
+ _smixer->stop();
+
+ if (_seekFile.size() > 0) {
+ delete _base;
+ _base = new FileChunk(_seekFile);
+
+ if (_seekPos > 0) {
+ assert(_seekPos > 8);
+ // In this case we need to get palette and number of frames
+ sub = _base->subBlock();
+ checkBlock(*sub, TYPE_AHDR);
+ handleAnimHeader(*sub);
+ delete sub;
+
+ _middleAudio = true;
+ _seekPos -= 8;
+ } else {
+ // We need this in Full Throttle when entering/leaving
+ // the old mine road.
+ tryCmpFile(_seekFile.c_str());
+ }
+ _skipPalette = false;
+ } else {
+ _skipPalette = true;
+ }
+
+ _base->seek(_seekPos, FileChunk::seek_start);
+ _frame = _seekFrame;
+
+ _seekPos = -1;
+ }
+
+ assert(_base);
+ if (_base->eof()) {
+ _vm->_smushVideoShouldFinish = true;
+ return;
+ }
+
+ sub = _base->subBlock();
+
+ switch (sub->getType()) {
+ case TYPE_AHDR: // FT INSANE may seek file to the beginning
+ handleAnimHeader(*sub);
+ break;
+ case TYPE_FRME:
+ handleFrame(*sub);
+ break;
+ default:
+ error("Unknown Chunk found at %x: %x, %d", _base->tell(), sub->getType(), sub->getSize());
+ }
+ delete sub;
+
+ _base->reseek();
+
+ if (_insanity)
+ _vm->_sound->processSound();
+
+ _vm->_imuseDigital->flushTracks();
+}
+
+void SmushPlayer::setPalette(const byte *palette) {
+ memcpy(_pal, palette, 0x300);
+ setDirtyColors(0, 255);
+}
+
+void SmushPlayer::setPaletteValue(int n, byte r, byte g, byte b) {
+ _pal[n * 3 + 0] = r;
+ _pal[n * 3 + 1] = g;
+ _pal[n * 3 + 2] = b;
+ setDirtyColors(n, n);
+}
+
+void SmushPlayer::setDirtyColors(int min, int max) {
+ if (_palDirtyMin > min)
+ _palDirtyMin = min;
+ if (_palDirtyMax < max)
+ _palDirtyMax = max;
+}
+
+void SmushPlayer::warpMouse(int x, int y, int buttons) {
+ _warpNeeded = true;
+ _warpX = x;
+ _warpY = y;
+ _warpButtons = buttons;
+}
+
+void SmushPlayer::updateScreen() {
+#ifdef DUMP_SMUSH_FRAMES
+ char fileName[100];
+ // change path below for dump png files
+ sprintf(fileName, "/path/to/somethere/%s%04d.png", _vm->getBaseName(), _frame);
+ FILE *file = fopen(fileName, "wb");
+ if (file == NULL)
+ error("can't open file for writing png");
+
+ png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
+ if (png_ptr == NULL) {
+ fclose(file);
+ error("can't write png header");
+ }
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+ if (info_ptr == NULL) {
+ fclose(file);
+ error("can't create png info struct");
+ }
+ if (setjmp(png_ptr->jmpbuf)) {
+ fclose(file);
+ error("png jmpbuf error");
+ }
+
+ png_init_io(png_ptr, file);
+
+ png_set_IHDR(png_ptr, info_ptr, _width, _height, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+ png_colorp palette = (png_colorp)png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof (png_color));
+ for (int i = 0; i != 256; ++i) {
+ (palette + i)->red = _pal[i * 3 + 0];
+ (palette + i)->green = _pal[i * 3 + 1];
+ (palette + i)->blue = _pal[i * 3 + 2];
+ }
+
+ png_set_PLTE(png_ptr, info_ptr, palette, PNG_MAX_PALETTE_LENGTH);
+
+ png_write_info(png_ptr, info_ptr);
+ png_set_flush(png_ptr, 10);
+
+ png_bytep row_pointers[480];
+ for (int y = 0 ; y < _height ; y++)
+ row_pointers[y] = (png_byte *) (_dst + y * _width);
+ png_write_image(png_ptr, row_pointers);
+ png_write_end(png_ptr, info_ptr);
+ png_free(png_ptr, palette);
+
+ fclose(file);
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+#endif
+
+ uint32 end_time, start_time = _vm->_system->getMillis();
+ _updateNeeded = true;
+ end_time = _vm->_system->getMillis();
+ debugC(DEBUG_SMUSH, "Smush stats: updateScreen( %03d )", end_time - start_time);
+}
+
+void SmushPlayer::insanity(bool flag) {
+ _insanity = flag;
+}
+
+void SmushPlayer::seekSan(const char *file, int32 pos, int32 contFrame) {
+ Common::StackLock lock(_mutex);
+
+ _seekFile = file ? file : "";
+ _seekPos = pos;
+ _seekFrame = contFrame;
+}
+
+void SmushPlayer::tryCmpFile(const char *filename) {
+ if (_compressedFile.isOpen()) {
+ _vm->_mixer->stopHandle(_compressedFileSoundHandle);
+ _compressedFile.close();
+ }
+ _compressedFileMode = false;
+ const char *i = strrchr(filename, '.');
+ if (i == NULL) {
+ error("invalid filename : %s", filename);
+ }
+#if defined(USE_MAD) || defined(USE_VORBIS)
+ char fname[260];
+#endif
+#ifdef USE_MAD
+ memcpy(fname, filename, i - filename);
+ strcpy(fname + (i - filename), ".mp3");
+ _compressedFile.open(fname);
+ if (_compressedFile.isOpen()) {
+ int size = _compressedFile.size();
+ _compressedFileMode = true;
+ _vm->_mixer->playInputStream(Audio::Mixer::kSFXSoundType, &_compressedFileSoundHandle, makeMP3Stream(&_compressedFile, size));
+ return;
+ }
+#endif
+#ifdef USE_VORBIS
+ memcpy(fname, filename, i - filename);
+ strcpy(fname + (i - filename), ".ogg");
+ _compressedFile.open(fname);
+ if (_compressedFile.isOpen()) {
+ int size = _compressedFile.size();
+ _compressedFileMode = true;
+ _vm->_mixer->playInputStream(Audio::Mixer::kSFXSoundType, &_compressedFileSoundHandle, makeVorbisStream(&_compressedFile, size));
+ return;
+ }
+#endif
+}
+
+void SmushPlayer::play(const char *filename, int32 offset, int32 startFrame) {
+
+ // Verify the specified file exists
+ ScummFile f;
+ _vm->openFile(f, filename);
+ if (!f.isOpen()) {
+ warning("SmushPlayer::play() File not found %s", filename);
+ return;
+ }
+ f.close();
+
+ _updateNeeded = false;
+ _warpNeeded = false;
+ _palDirtyMin = 256;
+ _palDirtyMax = -1;
+
+ // Hide mouse
+ bool oldMouseState = _vm->_system->showMouse(false);
+
+ // Load the video
+ _seekFile = filename;
+ _seekPos = offset;
+ _seekFrame = startFrame;
+ _base = 0;
+
+ setupAnim(filename);
+ init();
+
+ for (;;) {
+ if (_warpNeeded) {
+ _vm->_system->warpMouse(_warpX, _warpY);
+ _warpNeeded = false;
+ }
+ _vm->parseEvents();
+ _vm->processKbd(true);
+ if (_palDirtyMax >= _palDirtyMin) {
+ byte palette_colors[1024];
+ byte *p = palette_colors;
+
+ for (int i = _palDirtyMin; i <= _palDirtyMax; i++) {
+ byte *data = _pal + i * 3;
+
+ *p++ = data[0];
+ *p++ = data[1];
+ *p++ = data[2];
+ *p++ = 0;
+ }
+
+ _vm->_system->setPalette(palette_colors, _palDirtyMin, _palDirtyMax - _palDirtyMin + 1);
+
+ _palDirtyMax = -1;
+ _palDirtyMin = 256;
+ }
+ if (_updateNeeded) {
+ uint32 end_time, start_time;
+
+ start_time = _vm->_system->getMillis();
+ _vm->_system->copyRectToScreen(_dst, _width, 0, 0, _width, _height);
+ _vm->_system->updateScreen();
+ _updateNeeded = false;
+#ifdef _WIN32_WCE
+ _inTimer = false;
+ _inTimerCount = 0;
+#endif
+
+ end_time = _vm->_system->getMillis();
+
+ debugC(DEBUG_SMUSH, "Smush stats: BackendUpdateScreen( %03d )", end_time - start_time);
+
+ }
+ if (_vm->_smushVideoShouldFinish || _vm->_quit || _vm->_saveLoadFlag)
+ break;
+ _vm->_system->delayMillis(10);
+ }
+
+ release();
+
+ // Reset mouse state
+ _vm->_system->showMouse(oldMouseState);
+}
+
+} // End of namespace Scumm
+