diff --git a/video/module.mk b/video/module.mk
index 8f6ce8af839..797aab5b7ea 100644
--- a/video/module.mk
+++ b/video/module.mk
@@ -9,6 +9,7 @@ MODULE_OBJS := \
hnm_decoder.o \
mpegps_decoder.o \
mve_decoder.o \
+ paco_decoder.o \
psx_decoder.o \
qt_decoder.o \
smk_decoder.o \
diff --git a/video/paco_decoder.cpp b/video/paco_decoder.cpp
new file mode 100644
index 00000000000..0e90dd39371
--- /dev/null
+++ b/video/paco_decoder.cpp
@@ -0,0 +1,505 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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 3 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, see .
+ *
+ */
+
+#include "common/endian.h"
+#include "common/rect.h"
+#include "common/stream.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+#include "graphics/surface.h"
+#include "video/qt_data.h"
+#include "video/paco_decoder.h"
+
+
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+
+namespace Video {
+
+PacoDecoder::PacoDecoder() {
+}
+
+PacoDecoder::~PacoDecoder() {
+ close();
+}
+
+bool PacoDecoder::loadStream(Common::SeekableReadStream *stream) {
+ close();
+
+ stream->readUint16BE(); // 1
+ stream->readUint16BE(); // 0x26
+
+ uint16 width = stream->readUint16BE();
+ uint16 height = stream->readUint16BE();
+ int16 frameRate = stream->readUint16BE();
+ frameRate = ABS(frameRate); // Negative framerate is indicative of audio, but not always
+ uint16 flags = stream->readUint16BE();
+ bool hasAudio = (flags & 0x100) == 0x100;
+
+ stream->readUint32BE(); // maxChunksize
+ stream->readUint32BE(); // always 0
+ stream->readUint32BE(); // audio related flags
+ uint16 frameCount = stream->readUint16BE();
+ stream->readUint16BE(); // copy of frameCount
+ stream->readUint16BE(); // always 8?
+ stream->readUint16BE(); // always 0x600?
+ stream->readUint32BE(); // flags
+ stream->readUint16BE(); // 0
+
+ addTrack(new PacoVideoTrack(stream, frameRate, frameCount, hasAudio, width, height));
+ return true;
+}
+
+const Common::List *PacoDecoder::getDirtyRects() const {
+ const Track *track = getTrack(0);
+
+ if (track)
+ return ((const PacoVideoTrack *)track)->getDirtyRects();
+
+ return 0;
+}
+
+void PacoDecoder::clearDirtyRects() {
+ Track *track = getTrack(0);
+
+ if (track)
+ ((PacoVideoTrack *)track)->clearDirtyRects();
+}
+
+void PacoDecoder::copyDirtyRectsToBuffer(uint8 *dst, uint pitch) {
+ Track *track = getTrack(0);
+
+ if (track)
+ ((PacoVideoTrack *)track)->copyDirtyRectsToBuffer(dst, pitch);
+}
+
+PacoDecoder::PacoVideoTrack::PacoVideoTrack(
+ Common::SeekableReadStream *stream, uint16 frameRate, uint16 frameCount, bool hasAudio, uint16 width, uint16 height) {
+
+ _fileStream = stream;
+ _frameDelay = 1000 / frameRate; // frameRate is in fps, ms per frame
+ _frameCount = frameCount;
+
+ _surface = new Graphics::Surface();
+ _surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
+ _palette = new byte[3 * 256]();
+ memcpy(_palette, quickTimeDefaultPalette256, 256 * 3);
+ _dirtyPalette = false;
+
+ _curFrame = 0;
+ _nextFrameStartTime = 0;
+
+ for (uint i = 0; i < _frameCount; i++) {
+ _frameSizes[i] = stream->readUint32BE();
+ }
+}
+
+PacoDecoder::PacoVideoTrack::~PacoVideoTrack() {
+ delete _fileStream;
+ delete[] _palette;
+
+ _surface->free();
+ delete _surface;
+}
+
+bool PacoDecoder::PacoVideoTrack::endOfTrack() const {
+ return getCurFrame() >= getFrameCount();
+}
+
+uint16 PacoDecoder::PacoVideoTrack::getWidth() const {
+ return _surface->w;
+}
+
+uint16 PacoDecoder::PacoVideoTrack::getHeight() const {
+ return _surface->h;
+}
+
+Graphics::PixelFormat PacoDecoder::PacoVideoTrack::getPixelFormat() const {
+ return _surface->format;
+}
+
+
+enum frameTypes {
+ NOP = 0, // nop
+ // 1 - old initialisation data?
+ PALLETE = 2, // - new initialisation data (usually 0x30 0x00 0x00 ... meaning 8-bit with default QuickTime palette)
+ DELAY = 3, // - delay information
+ AUDIO = 4, // - audio data (8-bit unsigned PCM)
+ // 5 - should not be present
+ // 6 - should not be present
+ // 7 - unknown
+ VIDEO = 8, // - video frame
+ // 9 - unknown
+ // 10 - dummy?
+ EOC = 11 // - end of chunk marker
+};
+
+const Graphics::Surface *PacoDecoder::PacoVideoTrack::decodeNextFrame() {
+ uint32 nextFrame = _fileStream->pos() + _frameSizes[_curFrame];
+
+ debug(" frame %3d size %d @ %lX", _curFrame, _frameSizes[_curFrame], _fileStream->pos());
+
+ while (_fileStream->pos() < nextFrame) {
+ int64 currentPos = _fileStream->pos();
+ int frameType = _fileStream->readByte();
+ int v = _fileStream->readByte();
+ uint32 chunkSize = (v << 16 ) | _fileStream->readUint16BE();
+ debug(" slot type %d size %d @ %lX", frameType, chunkSize, _fileStream->pos() - 4);
+
+ switch (frameType) {
+ case AUDIO:
+ warning("PacoDecode::decodeFrame(): Audio not implemented");
+ break;
+ case VIDEO:
+ handleFrame(chunkSize - 4);
+ break;
+ case PALLETE:
+ warning("PacoDecode::decodeFrame(): Palette not implemented");
+ break;
+ case EOC:
+ break;
+ default:
+ error("PacoDecoder::decodeFrame(): unknown main chunk type (type = 0x%02X)", frameType);
+ break;
+ }
+ _fileStream->seek(currentPos + chunkSize);
+ }
+
+ _curFrame++;
+ _nextFrameStartTime += _frameDelay;
+
+ return _surface;
+}
+
+enum {
+ COPY = 0, // raw copy pixels
+ RLE, // RLE
+ PRLE, // pair RLE (read a pair of pixels and repeat it the specified number of times)
+ QRLE, // quad RLE (read four pixels and repeat it the specified number of times)
+ SKIP, // skip
+ ENDCURRENTLINE, // end current line and skip additional len lines
+ EOFRAME = 15 // not real opcode but 00 F0 00 00 is often seen at the end of frame and can serve as an end marker
+};
+
+#define ADJUST_LINE \
+ if (compr == 1) \
+ ypos2 = ypos; \
+ else { \
+ ypos2 = ypos * 2 - y; \
+ if (ypos2 >= y + bh) { \
+ ypos2 -= bh; \
+ if (!(bh & 1)) \
+ ypos2++; \
+ } \
+ }
+
+#define PUTPIX(pix) \
+ do { \
+ *dst++ = pix; \
+ xpos++; \
+ } while(0);
+
+#define SKIP() \
+ do { \
+ dst++; \
+ xpos++; \
+ } while(0);
+
+
+void PacoDecoder::PacoVideoTrack::handleFrame(uint32 chunkSize) {
+ uint16 w = getWidth();
+
+ uint16 x = _fileStream->readUint16BE(); // x offset of the updated area
+ uint16 y = _fileStream->readUint16BE(); // y offset of the updated area
+ uint16 bw = _fileStream->readUint16BE(); // updated area width
+ uint16 bh = _fileStream->readUint16BE(); // updated area height
+ uint compr = _fileStream->readByte(); // compression method and flags
+ _fileStream->readByte(); // padding
+
+ debug(" +%d,%d - %dx%d compr %X", x, y, bw, bh, compr);
+
+ compr = compr & 0xF;
+
+ uint8 *fdata = new uint8[1048576]; // 0x100000 copied from original pacodec
+ _fileStream->read(fdata, chunkSize - 10); // remove header length
+ debug("pos: %ld", _fileStream->pos());
+ int16 xpos = x, ypos = y, ypos2 = y;
+ byte *dst = (byte *)_surface->getPixels() + x + y * w;
+
+ const uint8 *src = fdata;
+ int16 i, c, c1, c2, c3, c4;
+ uint8 clrs[16];
+
+
+ while (ypos < y + bh) {
+ c = *src++;
+ debug("debug info: ypos %d y %d bh %d src: %d", ypos, y, bh, c);
+
+ if (c == 0 ){ // long operation
+ int16 op = src[0] >> 4;
+ int16 len = ((src[0] & 0xF) << 8) | src[1];
+ src += 2;
+ debug(" long operation: opcode: %d", op);
+ switch (op) {
+ case COPY:
+ while (len--)
+ PUTPIX(*src++);
+ break;
+ case RLE:
+ c1 = *src++;
+ while (len--)
+ PUTPIX(c1);
+ break;
+ case PRLE:
+ c1 = *src++;
+ c2 = *src++;
+ while (len--){
+ PUTPIX(c1);
+ PUTPIX(c2);
+ }
+ break;
+ case QRLE:
+ c1 = *src++;
+ c2 = *src++;
+ c3 = *src++;
+ c4 = *src++;
+ while (len--) {
+ PUTPIX(c1);
+ PUTPIX(c2);
+ PUTPIX(c3);
+ PUTPIX(c4);
+ }
+ break;
+ case SKIP:
+ while (len--)
+ SKIP();
+ break;
+ case ENDCURRENTLINE:
+ xpos = x;
+ ypos += len + 1;
+ ADJUST_LINE;
+ dst = (byte *)_surface->getPixels() + xpos + ypos2 * w;
+ break;
+ case EOFRAME:
+ xpos = x;
+ ypos = y + bh;
+ break;
+ default:
+ PUTPIX(0xFF);
+ debug("PacoDecoder::PacoVideoTrack::handleFrame: Long op: 0x0 op %d", op);
+ }
+
+ } else if (c < 128) { // copy the same amount of pixels
+ debug(" copy pixels: %d", c);
+ while (c--)
+ PUTPIX(*src++);
+ } else if (c < 254) { // repeat the following value 256 - op times
+ debug(" copy pixels -op: %d", 256 - c);
+ c1 = *src++;
+ c = 256 - c;
+ while (c--)
+ PUTPIX(c1);
+ } else if (c < 255) {
+ // next byte is either the number of pixels to skip (if non-zero) or
+ // a signal of compact RLE mode
+ c = *src++;
+
+ if (!c) { // compact RLE mode
+ unsigned mask = (src[0] << 8) | src[1];
+ src += 2;
+ debug("debug info compact RLE: c: %d mask: %d", c, mask);
+
+ for (i = 0; i < 16; i++, mask >>= 1) {
+ if (mask & 1)
+ clrs[i] = *src++;
+ }
+ while (xpos < x + bw) {
+ int16 op = *src++;
+ int16 len = op & 0xF;
+ op >>= 4;
+ if (op == 0) { // low nibble....
+ op = len;
+ len = *src++;
+ debug("debug info compact: op: %d", op);
+ switch (op) {
+ case COPY:
+ debug("debug info COPY: %d", len);
+ while (len--) {
+ c = *src++;
+ PUTPIX(clrs[c >> 4]);
+ if (!len)
+ break;
+ len--;
+ PUTPIX(clrs[c & 0xF]);
+ }
+ break;
+ case RLE:
+ debug("debug info RLE: %d", len);
+ c = *src++;
+ while (len--)
+ PUTPIX(clrs[c & 0xF]);
+ break;
+ case PRLE:
+ debug("debug info PRLE: %d", len);
+ c = *src++;
+ c1 = clrs[c >> 4];
+ c2 = clrs[c & 0xF];
+ while (len--) {
+ PUTPIX(c1);
+ PUTPIX(c2);
+ }
+ break;
+ case QRLE:
+ debug("debug info QRLE: %d", len);
+ c = *src++;
+ c1 = clrs[c >> 4];
+ c2 = clrs[c & 0xF];
+ c = *src++;
+ c3 = clrs[c >> 4];
+ c4 = clrs[c & 0xF];
+ while (len--) {
+ PUTPIX(c1);
+ PUTPIX(c2);
+ PUTPIX(c3);
+ PUTPIX(c4);
+ }
+ break;
+ case SKIP:
+ debug("debug info SKIP: %d", len);
+ while (len--)
+ SKIP();
+ break;
+ case ENDCURRENTLINE:
+ debug("debug info ENDCURRENTLINE: %d", len);
+ xpos = x + bw;
+ break;
+ default:
+ warning("PacoDecoder::PacoVideoTrack::handleFrame: Compact RLE mode: 0x0 op %d", op);
+ }
+ } else if (op < 8) { // copy 1-7 colors
+ debug("debug info copy 1-7 colors: %d", len);
+ PUTPIX(clrs[len]);
+ op--;
+ while (op--) {
+ c = *src++;
+ PUTPIX(clrs[c >> 4]);
+ if (!op)
+ break;
+ op--;
+ PUTPIX(clrs[c & 0xF]);
+ }
+ } else if (op < 14) { // repeat color
+ debug("debug info Repeat color: %d", len);
+ op = 16 - op;
+ while (op--)
+ PUTPIX(clrs[len]);
+ } else if (op < 15) { // skip number of pixels in low nibbel
+ debug("debug info Skip number of pixels: %d", len);
+ while (len--)
+ SKIP();
+ } else {
+ if (len < 8) { // Pair run
+ debug("debug info pair run: %d", len);
+ c = *src++;
+ c1 = clrs[c >> 4];
+ c2 = clrs[c & 0xF];
+ while (len--) {
+ PUTPIX(c1);
+ PUTPIX(c2);
+ }
+ } else { // Quad run
+ debug("debug info quad run: %d", len);
+ len = 16 - len;
+ c = *src++;
+ c1 = clrs[c >> 4];
+ c2 = clrs[c & 0xF];
+ c = *src++;
+ c3 = clrs[c >> 4];
+ c4 = clrs[c & 0xF];
+ while (len--) {
+ PUTPIX(c1);
+ PUTPIX(c2);
+ PUTPIX(c3);
+ PUTPIX(c4);
+ }
+ }
+ }
+ }
+ } else {
+ debug("debug info SKIP: %d", c);
+ while (c--)
+ SKIP();
+ }
+ } else {
+ // pair or quad run. Read the next byte and if it is below 128 then read and
+ // repeat a pair of pixels len times, otherwise read and repeat four pixels
+ // (but 256 - len times)
+ c = *src++;
+ if (c < 128) { // pair run
+ debug("debug info PAIR RUN: %d", c);
+
+ c1 = *src++;
+ c2 = *src++;
+ while (c--) {
+ PUTPIX(c1);
+ PUTPIX(c2);
+ }
+ } else { // quad run
+ debug("debug info QUAD RUN: %d", c);
+ c = 256 - c;
+ c1 = *src++;
+ c2 = *src++;
+ c3 = *src++;
+ c4 = *src++;
+ while (c--) {
+ PUTPIX(c1);
+ PUTPIX(c2);
+ PUTPIX(c3);
+ PUTPIX(c4);
+ }
+ }
+ }
+ if (xpos > x + bw) debug("!!!");
+ if (xpos >= x + bw) {
+ debug("debug info ADJUST LINE");
+ xpos = x;
+ ypos++;
+ ADJUST_LINE;
+ dst = (byte *)_surface->getPixels() + x + ypos2 * w;
+ }
+ }
+
+ _dirtyRects.clear();
+ _dirtyRects.push_back(Common::Rect(x, y, bw, bh));
+
+}
+
+void PacoDecoder::PacoVideoTrack::copyDirtyRectsToBuffer(uint8 *dst, uint pitch) {
+ for (Common::List::const_iterator it = _dirtyRects.begin(); it != _dirtyRects.end(); ++it) {
+ for (int y = (*it).top; y < (*it).bottom; ++y) {
+ const int x = (*it).left;
+ memcpy(dst + y * pitch + x, (byte *)_surface->getBasePtr(x, y), (*it).right - x);
+ }
+ }
+ clearDirtyRects();
+}
+
+} // End of namespace Video
diff --git a/video/paco_decoder.h b/video/paco_decoder.h
new file mode 100644
index 00000000000..7ac0b51aa1a
--- /dev/null
+++ b/video/paco_decoder.h
@@ -0,0 +1,103 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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 3 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, see .
+ *
+ */
+
+#ifndef VIDEO_PACODECODER_H
+#define VIDEO_PACODECODER_H
+
+#include "video/video_decoder.h"
+#include "common/list.h"
+#include "common/rect.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Graphics {
+struct PixelFormat;
+struct Surface;
+}
+
+namespace Video {
+
+/**
+ * Decoder for PACo videos.
+ *
+ * Video decoder used in engines:
+ * - director
+ */
+class PacoDecoder : public VideoDecoder {
+public:
+ PacoDecoder();
+ virtual ~PacoDecoder();
+
+ virtual bool loadStream(Common::SeekableReadStream *stream);
+
+ const Common::List *getDirtyRects() const;
+ void clearDirtyRects();
+ void copyDirtyRectsToBuffer(uint8 *dst, uint pitch);
+
+protected:
+ class PacoVideoTrack : public VideoTrack {
+ public:
+ PacoVideoTrack(
+ Common::SeekableReadStream *stream, uint16 frameRate, uint16 frameCount,
+ bool hasAudio, uint16 width, uint16 height);
+ ~PacoVideoTrack();
+
+ bool endOfTrack() const;
+ virtual bool isRewindable() const { return false; }
+
+ uint16 getWidth() const;
+ uint16 getHeight() const;
+ Graphics::PixelFormat getPixelFormat() const;
+ int getCurFrame() const { return _curFrame; }
+ int getFrameCount() const { return _frameCount; }
+ uint32 getNextFrameStartTime() const { return _nextFrameStartTime; }
+ virtual const Graphics::Surface *decodeNextFrame();
+ virtual void handleFrame(uint32 chunkSize);
+ const byte *getPalette() const { return _palette; }
+ bool hasDirtyPalette() const { return false; }
+
+ const Common::List *getDirtyRects() const { return &_dirtyRects; }
+ void clearDirtyRects() { _dirtyRects.clear(); }
+ void copyDirtyRectsToBuffer(uint8 *dst, uint pitch);
+
+ protected:
+ Common::SeekableReadStream *_fileStream;
+ Graphics::Surface *_surface;
+
+ int _curFrame;
+
+ byte *_palette;
+ int _frameSizes[65536]; // can be done differently?
+ mutable bool _dirtyPalette;
+
+ uint32 _frameCount;
+ uint32 _frameDelay;
+ uint32 _nextFrameStartTime;
+
+ Common::List _dirtyRects;
+ };
+};
+
+} // End of namespace Video
+
+#endif