[Scummvm-git-logs] scummvm master -> c4baf30f9bd88148a961ba592d8c5120486175e5

sev- sev at scummvm.org
Sun Mar 14 18:16:28 UTC 2021


This automated email contains information about 4 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
1947eae043 AUDIO: Add support for Xan DPCM (Crusader: No Regret)
017a801c96 IMAGE: Add support for Xan codec videos (Crusader, Wing Commander)
a20b8781e1 VIDEO: Add support for Xan codec videos (Crusader, Wing Commander)
c4baf30f9b ULTIMA8: Add Crusader: No Regret movies


Commit: 1947eae043bf71a60e132f956913452257dd068e
    https://github.com/scummvm/scummvm/commit/1947eae043bf71a60e132f956913452257dd068e
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-03-14T19:16:24+01:00

Commit Message:
AUDIO: Add support for Xan DPCM (Crusader: No Regret)

Changed paths:
  A audio/decoders/xan_dpcm.cpp
  A audio/decoders/xan_dpcm.h
    audio/decoders/wave_types.h
    audio/module.mk


diff --git a/audio/decoders/wave_types.h b/audio/decoders/wave_types.h
index 0b64fab6d0..c600b02bc1 100644
--- a/audio/decoders/wave_types.h
+++ b/audio/decoders/wave_types.h
@@ -34,7 +34,8 @@ enum WaveCompressionType {
 	kWaveFormatMP3         = 0x0055,
 	kWaveFormatDK3         = 0x0062, // rogue format number
 	kWaveFormatMSIMAADPCM2 = 0x0069,
-	kWaveFormatWMAv2       = 0x0161
+	kWaveFormatWMAv2       = 0x0161,
+	kWaveFormatXanDPCM     = 0x594a  // 'JY', Crusader: No Regret videos
 };
 
 } // End of namespace Audio
diff --git a/audio/decoders/xan_dpcm.cpp b/audio/decoders/xan_dpcm.cpp
new file mode 100644
index 0000000000..0c2f221ba6
--- /dev/null
+++ b/audio/decoders/xan_dpcm.cpp
@@ -0,0 +1,105 @@
+/* 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 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.
+ *
+ */
+
+#include "common/stream.h"
+#include "common/util.h"
+#include "common/textconsole.h"
+
+#include "audio/audiostream.h"
+#include "audio/decoders/xan_dpcm.h"
+
+
+namespace Audio {
+
+/**
+ * Implements the Xan DPCM decoder used in Crusader: No Regret and Wing
+ * Commander IV movies.  Implementation based on the description on the
+ * MultiMedia Wiki:
+ * https://wiki.multimedia.cx/index.php/Xan_DPCM
+ */
+class Xan_DPCMStream : public Audio::AudioStream {
+public:
+	Xan_DPCMStream(int rate, int channels, Common::SeekableReadStream *data) : _rate(rate), _channels(channels), _data(data) {
+		assert(channels == 1 || channels == 2);
+		_pred[0] = data->readSint16LE();
+		if (channels == 2)
+			_pred[1] = data->readSint16LE();
+		_shift[0] = 4;
+		_shift[1] = 4;
+	};
+
+	virtual int readBuffer(int16 *buffer, const int numSamples) override {
+		int i = 0;
+		for (; i < numSamples; i++) {
+			int32 *pshift = ((_channels == 2 && (i % 2)) ? _shift + 1 : _shift);
+			int32 *ppred  = ((_channels == 2 && (i % 2)) ? _pred + 1 : _pred);
+			const uint8 b = _data->readByte();
+			const int diff = static_cast<int8>(b & 0xFC) * 256;
+			if ((b & 3) == 3)
+				*pshift += 1;
+			else
+				*pshift -= (2 * (b & 3));
+			if (*pshift < 0)
+				*pshift = 0;
+			if (*pshift > 15) {
+				warning("Xan DPCM shift should not go over 15, corrupt data?");
+				*pshift = 15;
+			}
+			*ppred += (diff >> *pshift);
+			*ppred = CLIP(*ppred, -32768, 32767);
+			*buffer = *ppred;
+			buffer++;
+			if (_data->eos())
+				break;
+		}
+		return i;
+	}
+
+	virtual bool isStereo() const override {
+		return _channels == 2;
+	}
+
+	virtual int getRate() const override {
+		return _rate;
+	}
+
+	virtual bool endOfData() const override {
+		return _data->eos();
+	}
+
+private:
+	int _channels;
+	int _rate;
+	Common::SeekableReadStream *_data;
+	int32 _pred[2];
+	int32 _shift[2];
+};
+
+XanDPCMStream::XanDPCMStream(int rate, int channels) :
+	Audio::StatelessPacketizedAudioStream(rate, channels) {
+}
+
+AudioStream *XanDPCMStream::makeStream(Common::SeekableReadStream *data) {
+	return new Xan_DPCMStream(getRate(), getChannels(), data);
+}
+
+} // End of namespace Audio
diff --git a/audio/decoders/xan_dpcm.h b/audio/decoders/xan_dpcm.h
new file mode 100644
index 0000000000..8fd939c6fc
--- /dev/null
+++ b/audio/decoders/xan_dpcm.h
@@ -0,0 +1,66 @@
+/* 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 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.
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ *  - ultima8 (Crusader: No Regret)
+ */
+
+#ifndef AUDIO_XAN_DPCM_H
+#define AUDIO_XAN_DPCM_H
+
+#include "common/scummsys.h"
+#include "common/types.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Audio {
+
+class StatelessPacketizedAudioStream;
+
+/**
+ * Implements the Xan DPCM decoder used in Crusader: No Regret and Wing
+ * Commander IV movies.  Implementation based on the description on the
+ * MultiMedia Wiki:
+ * https://wiki.multimedia.cx/index.php/Xan_DPCM
+ */
+class XanDPCMStream : public StatelessPacketizedAudioStream {
+public:
+	/**
+	 * Create a Xan DPCM stream
+	 * @param rate              sampling rate (samples per second)
+	 * @param channels  	number of channels to decode
+	 * @return   a new XanDPCMStream, or NULL, if an error occurred
+	 */
+	 XanDPCMStream(int rate, int channels);
+
+protected:
+	virtual AudioStream *makeStream(Common::SeekableReadStream *data) override;
+};
+
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/module.mk b/audio/module.mk
index 4456f87a3b..4b795bc786 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -35,6 +35,7 @@ MODULE_OBJS := \
 	decoders/wave.o \
 	decoders/wma.o \
 	decoders/xa.o \
+	decoders/xan_dpcm.o \
 	mods/infogrames.o \
 	mods/maxtrax.o \
 	mods/mod_xm_s3m.o \


Commit: 017a801c96a650b18bf607a7d65ad292dd6445b3
    https://github.com/scummvm/scummvm/commit/017a801c96a650b18bf607a7d65ad292dd6445b3
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-03-14T19:16:24+01:00

Commit Message:
IMAGE: Add support for Xan codec videos (Crusader, Wing Commander)

Changed paths:
  A image/codecs/xan.cpp
  A image/codecs/xan.h
    image/codecs/codec.cpp
    image/module.mk


diff --git a/image/codecs/codec.cpp b/image/codecs/codec.cpp
index 8716a01d4e..4e161428d2 100644
--- a/image/codecs/codec.cpp
+++ b/image/codecs/codec.cpp
@@ -43,6 +43,7 @@
 #include "image/codecs/smc.h"
 #include "image/codecs/svq1.h"
 #include "image/codecs/truemotion1.h"
+#include "image/codecs/xan.h"
 
 #include "common/endian.h"
 #include "common/textconsole.h"
@@ -224,6 +225,8 @@ Codec *createBitmapCodec(uint32 tag, uint32 streamTag, int width, int height, in
 		return new Indeo4Decoder(width, height, bitsPerPixel);
 	case MKTAG('I', 'V', '5', '0'):
 		return new Indeo5Decoder(width, height, bitsPerPixel);
+	case MKTAG('X', 'x', 'a', 'n'):
+		return new XanDecoder(width, height, bitsPerPixel);
 #ifdef IMAGE_CODECS_TRUEMOTION1_H
 	case MKTAG('D','U','C','K'):
 	case MKTAG('d','u','c','k'):
diff --git a/image/codecs/xan.cpp b/image/codecs/xan.cpp
new file mode 100644
index 0000000000..d46c3b9197
--- /dev/null
+++ b/image/codecs/xan.cpp
@@ -0,0 +1,502 @@
+/* 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 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.
+ */
+
+/**
+ * Xan image decoder. (fourcc Xxan)
+ *
+ * Used by Crusader: No Regret AVI files
+ *
+ * This code was created based on the multimedia wiki:
+ * https://wiki.multimedia.cx/index.php/Origin_Xan_Codec
+ * and ffmpeg's libavcodec/xxan.c.
+ * The ffmpeg code is LGPL2 licensed and Copyright (C) 2011
+ * Konstantin Shishkov based on work by Mike Melanson.
+ *
+ * A similar format is used in Wing Commander III (although not in an AVI
+ * container) and IV.
+ */
+
+#include "image/codecs/xan.h"
+
+#include "common/stream.h"
+#include "common/bitstream.h"
+#include "common/memstream.h"
+#include "common/util.h"
+#include "common/textconsole.h"
+#include "common/system.h"
+#include "common/debug.h"
+#include "common/rect.h"
+#include "graphics/surface.h"
+#include "graphics/yuv_to_rgb.h"
+
+namespace Image {
+
+static const int SCRATCH_SPARE = 256;
+
+XanDecoder::XanDecoder(int width, int height, int bitsPerPixel) : Codec(),
+		_width(width), _height(height), _wc4Mode(false) {
+	assert(bitsPerPixel == 16);
+	if (bitsPerPixel != 16)
+		error("XanDecoder: BPP must be 16 not %d", bitsPerPixel);
+	if (width % 2)
+		error("XanDecoder: width must be even, not %d", width);
+	_surface.create(_width, _height, getPixelFormat());
+	_scratchbuf = new uint8[_width * _height + SCRATCH_SPARE];
+	_lumabuf = new uint8[_width * _height];
+	_ybuf = new uint8[_width * _height];
+	_ubuf = new uint8[_width * _height / 2];
+	_vbuf = new uint8[_width * _height / 2];
+	memset(_scratchbuf, 0, _width * _height + SCRATCH_SPARE);
+	memset(_lumabuf, 0, _width * _height);
+	memset(_ybuf, 0, _width * _height);
+	memset(_ubuf, 127, _width * _height / 2);
+	memset(_vbuf, 127, _width * _height / 2);
+}
+
+XanDecoder::~XanDecoder() {
+	_surface.free();
+	delete [] _scratchbuf;
+	delete [] _lumabuf;
+	delete [] _ybuf;
+	delete [] _ubuf;
+	delete [] _vbuf;
+}
+
+const Graphics::Surface *XanDecoder::decodeFrame(Common::SeekableReadStream &stream) {
+	uint32 frametype = stream.readUint32LE();
+	if (frametype > 1) {
+		error("Xan frametype should be 0 or 1, got %d", frametype);
+	}
+
+	if (frametype == 0) {
+		decodeFrameType0(stream);
+	} else {
+		decodeFrameType1(stream);
+	}
+
+	return &_surface;
+}
+
+// An unoptimized version of the one from libavutil, but works fine
+static void _memcpy_backptr(uint8 *dst, int back, int cnt) {
+	if (cnt <= 0 || back <= 0)
+		return;
+
+	const uint8 *src = &dst[-back];
+	if (back == 1) {
+		uint8 val = *src;
+		memset(dst, val, cnt);
+	} else {
+		int blocklen = back;
+		while (cnt > blocklen) {
+			memcpy(dst, src, blocklen);
+			dst += blocklen;
+			cnt -= blocklen;
+			blocklen <<= 1;
+		}
+		memcpy(dst, src, cnt);
+	}
+}
+
+// Unpack using the WC3 algorithm
+static int _unpack(Common::SeekableReadStream &stream, uint8 *dest, const int dest_len) {
+	const uint8 *orig_dest = dest;
+	const uint8 *dest_end = dest + dest_len;
+
+	memset(dest, 0, dest_len);
+
+	while (dest < dest_end) {
+		if (stream.eos())
+			return -1;
+
+		const uint8 opcode = stream.readByte();
+
+		if (opcode < 0xe0) {
+			int readsize, copysize, back;
+			if ((opcode & 0x80) == 0) {
+				readsize = opcode & 3;
+				back     = ((opcode & 0x60) << 3) + stream.readByte() + 1;
+				copysize = ((opcode & 0x1c) >> 2) + 3;
+			} else if ((opcode & 0x40) == 0) {
+				const uint8 b = stream.readByte();
+				readsize = b >> 6;
+				back     = ((b & 0x3f) << 8) + stream.readByte() + 1;
+				copysize = (opcode & 0x3f) + 4;
+			} else {
+				readsize = opcode & 3;
+				back     = ((opcode & 0x10) << 12) + stream.readUint16BE() + 1;
+				copysize = ((opcode & 0x0c) <<  6) + stream.readByte() + 5;
+				if (readsize + copysize > dest_end - dest)
+					break;
+			}
+			if (dest + readsize + copysize > dest_end ||
+				dest - orig_dest + readsize < back)
+				return -1;
+			stream.read(dest, readsize);
+			dest += readsize;
+			_memcpy_backptr(dest, back, copysize);
+			dest += copysize;
+		} else {
+			bool finish = (opcode >= 0xfc);
+
+			int readsize = finish ? opcode & 3 : ((opcode & 0x1f) * 4) + 4;
+			if (dest + readsize > dest_end)
+				return -1;
+			stream.read(dest, readsize);
+			dest += readsize;
+			if (finish)
+				break;
+		}
+	}
+
+	return dest - orig_dest;
+}
+
+
+bool XanDecoder::decodeChroma(Common::SeekableReadStream &stream, const int chroma_off) {
+	if (!chroma_off)
+		return 0;
+
+	if (chroma_off + 4 >= stream.size() - stream.pos()) {
+		warning("Invalid chroma block position");
+		return false;
+	}
+
+	stream.seek(chroma_off + 4);
+
+	const int mode = stream.readUint16LE();
+	const int table_start = stream.pos();
+	// 2 bytes per table entry
+	const int table_size = stream.readUint16LE() * 2;
+
+	if (mode > 1) {
+		warning("Unexpected chroma mode %d", mode);
+		return false;
+	}
+
+	if (table_size >= stream.size() - stream.pos()) {
+		warning("Invalid chroma block offset");
+		return false;
+	}
+
+	stream.skip(table_size);
+	const int dec_size = _unpack(stream, _scratchbuf, _width * _height);
+	if (dec_size < 0) {
+		warning("Chroma unpacking failed");
+		return false;
+	}
+
+	const int pitch = _width / 2;
+
+	uint8 *U = _ubuf;
+	uint8 *V = _vbuf;
+	const uint8 *src = _scratchbuf;
+	const uint8 *src_end = src + dec_size;
+	if (mode) {
+		// YUV420 frame
+		for (int y = 0; y < _height / 2; y++) {
+			for (int x = 0; x < pitch; x++) {
+				if (src >= src_end)
+					return true;
+				int toff = *src++ * 2;
+				if (toff) {
+					if (toff > table_size)
+						return false;
+					const int pos = stream.pos();
+					stream.seek(table_start + toff);
+					uint8 uval, vval;
+					if (_wc4Mode) {
+						uint16 val = stream.readUint16LE();
+						uval = (val >> 3) & 0xF8;
+						vval = (val >> 8) & 0xF8;
+					} else {
+						uval = stream.readByte();
+						vval = stream.readByte();
+					}
+					uval = uval | uval >> 5;
+					vval = vval | vval >> 5;
+					stream.seek(pos);
+					U[x] = uval;
+					V[x] = vval;
+				}
+			}
+			U += pitch;
+			V += pitch;
+		}
+		if (_height % 1) {
+			memcpy(U, U - pitch, pitch);
+			memcpy(V, V - pitch, pitch);
+		}
+	} else {
+		// YUV410 frame - expand out U and V components
+		uint8 *U2 = U + pitch;
+		uint8 *V2 = V + pitch;
+
+		for (int y = 0; y < _height / 4; y++) {
+			for (int x = 0; x < pitch; x += 2) {
+				if (src >= src_end)
+					return true;
+				int toff = *src++ * 2;
+				if (toff) {
+					if (toff > table_size)
+						return false;
+					const int pos = stream.pos();
+					stream.seek(table_start + toff);
+					uint8 uval, vval;
+					if (_wc4Mode) {
+						uint16 val = stream.readUint16LE();
+						uval = (val >> 3) & 0xF8;
+						vval = (val >> 8) & 0xF8;
+					} else {
+						uval = stream.readByte();
+						vval = stream.readByte();
+					}
+					uval = uval | uval >> 5;
+					vval = vval | vval >> 5;
+					stream.seek(pos);
+					U[x] = U[x + 1] = U2[x] = U2[x + 1] = uval;
+					V[x] = V[x + 1] = V2[x] = V2[x + 1] = vval;
+				}
+			}
+			U  += pitch * 2;
+			V  += pitch * 2;
+			U2 += pitch * 2;
+			V2 += pitch * 2;
+		}
+		if (_height % 4) {
+			int lines = ((_height + 1) / 2) - (_height / 4) * 2;
+
+			memcpy(U, U - lines * pitch, lines * pitch);
+			memcpy(V, V - lines * pitch, lines * pitch);
+		}
+	}
+
+	return true;
+}
+
+void XanDecoder::decodeFrameType0(Common::SeekableReadStream &stream) {
+	const uint32 chroma_offset = stream.readUint32LE();
+	const uint32 refines_offset = stream.readUint32LE();
+	const uint32 luma_offset = stream.pos();
+	const uint32 nbytes = static_cast<uint32>(stream.size());
+
+	if (chroma_offset > nbytes || refines_offset > nbytes) {
+		error("invalid frame type 0 offsets");
+	}
+
+	if (!decodeChroma(stream, chroma_offset)) {
+		warning("xxan chrome decode failed frame type 0");
+		return;
+	}
+
+	stream.seek(luma_offset);
+	decompressLuma(stream);
+
+	//
+	// Expand out the decompressed luma data. For type 0 frames:
+	// * luma vals are 5-bit diffs, where
+	//   * top row values are diffs on the last value
+	//   * and the remaining rows are deltas on the value above.
+	// * every second pixel in x is linearly interpolated from its horizontal neighbours.
+	// * output values are clipped to 6 bits.
+	// * a set of refinements values can adjust luma of interpolated pixels
+	//
+	const uint8 *lumadecomp = _scratchbuf;
+	uint8 *lumarow = _lumabuf;
+	int last = *lumadecomp++;
+	lumarow[0] = last * 2;
+	int x;
+	// The top row uses only the left value for prediction
+	for (x = 1; x < _surface.w - 1; x += 2) {
+		int cur = (last + *lumadecomp++) & 0x1F;
+		lumarow[x] = last + cur;
+		lumarow[x + 1] = cur * 2;
+		last = cur;
+	}
+	lumarow[x] = last * 2;
+	uint8 const *last_lumarow = lumarow;
+	lumarow += _surface.w;
+
+	// The remaining rows
+	for (int y = 1; y < _surface.h; y++) {
+		last = ((last_lumarow[0] / 2) + *lumadecomp++) & 0x1F;
+		lumarow[0] = last * 2;
+		for (x = 1; x < _surface.w - 1; x += 2) {
+			int cur = ((last_lumarow[x + 1] / 2) + *lumadecomp++) & 0x1F;
+			lumarow[x] = last + cur;
+			lumarow[x + 1] = cur * 2;
+			last = cur;
+		}
+		lumarow[x] = last * 2;
+		last_lumarow = lumarow;
+		lumarow += _surface.w;
+	}
+
+	if (refines_offset) {
+		stream.seek(refines_offset + 8);
+
+		int dec_size = _unpack(stream, _scratchbuf, _width * _height / 2);
+		if (dec_size < 0) {
+			warning("luma refine unpacking failed!");
+			dec_size = 0;
+		} else {
+			dec_size = MIN(dec_size, _width * _height / 2 - 1);
+		}
+
+		for (int i = 0; i < dec_size; i++)
+			_lumabuf[i * 2 + 1] = (_lumabuf[i * 2 + 1] + _scratchbuf[i] * 2) & 0x3F;
+	}
+
+	convertYUVtoRGBSurface();
+}
+
+void XanDecoder::decodeFrameType1(Common::SeekableReadStream &stream) {
+	const uint32 chroma_offset = stream.readUint32LE();
+	const uint32 refines_offset = stream.readUint32LE();
+	const uint32 refine2_offset = stream.readUint32LE();
+	const uint32 luma_offset = stream.pos();
+	const uint32 nbytes = static_cast<uint32>(stream.size());
+
+	if (chroma_offset > nbytes || refines_offset > nbytes || refine2_offset > nbytes) {
+		error("invalid frame type 1 offset");
+	}
+
+	if (!decodeChroma(stream, chroma_offset)) {
+		warning("xxan chrome decode failed frame type 1");
+		return;
+	}
+
+	stream.seek(luma_offset);
+	decompressLuma(stream);
+
+	//
+	// Expand out the decompressed luma data. For type 1 frames:
+	// * luma vals are 5-bit diffs on the previous frame's values
+	// * every second pixel in x is linearly interpolated from its horizontal neighbours.
+	// * output values are clipped to 6 bits.
+	// * a set of refinements values can adjust luma of interpolated pixels
+	//
+	const uint8 *lumadecomp = _scratchbuf;
+	uint8 *lumarow = _lumabuf;
+	for (int y = 0; y < _surface.h; y++) {
+		int x;
+		int last = (lumarow[0] + (*lumadecomp++ * 2)) & 0x3F;
+		lumarow[0] = last;
+		for (x = 1; x < _surface.w - 1; x += 2) {
+			int cur = (lumarow[x + 1] + (*lumadecomp++ * 2)) & 0x3F;
+			lumarow[x] = (last + cur) / 2;
+			lumarow[x + 1] = cur;
+			last = cur;
+		}
+		lumarow[x] = last;
+		lumarow += _surface.w;
+	}
+
+	if (refines_offset) {
+		stream.seek(refines_offset + 8);
+
+		int dec_size = _unpack(stream, _scratchbuf, _width * _height / 2);
+
+		if (dec_size < 0) {
+			warning("luma refine unpacking failed!");
+			dec_size = 0;
+		} else {
+			dec_size = MIN(dec_size, _width * _height / 2 - 1);
+		}
+
+		int dec2_size = 0;
+		uint8 *scratch2 = _scratchbuf + _width * _height / 2;
+		if (refine2_offset) {
+			stream.seek(refine2_offset + 8);
+			dec2_size = _unpack(stream, scratch2, _width * _height / 2);
+			if (dec2_size < 0) {
+				warning("luma refine2 unpacking failed!");
+				dec2_size = 0;
+			} else {
+				dec2_size = MIN(dec_size, _width * _height / 2 - 1);
+			}
+		}
+
+		for (int i = 0; i < dec_size; i++) {
+			int adjust = _scratchbuf[i] * 2;
+			if (dec2_size)
+				adjust += scratch2[i] * 2;
+			_lumabuf[i * 2 + 1] = (_lumabuf[i * 2 + 1] + adjust) & 0x3F;
+		}
+	}
+
+	convertYUVtoRGBSurface();
+}
+
+void XanDecoder::decompressLuma(Common::SeekableReadStream &stream) {
+	const int32 startpos = stream.pos();
+	const int nsymbols = stream.readByte();
+	const int eofsymbol = stream.readByte();
+
+	const int root = nsymbols + eofsymbol;
+	const uint8 *lumaend = _scratchbuf + _surface.w * _surface.h;
+
+	stream.skip(nsymbols * 2);
+
+	uint8 *luma = _scratchbuf;
+	int node = root;
+	int bits = stream.readByte();
+	int mask = 0x80;
+	while (!stream.eos()) {
+		const int bit = ((bits & mask) ? 1 : 0);
+		mask >>= 1;
+
+		const int32 nextbitspos = stream.pos();
+		stream.seek(startpos + node * 2 + bit - eofsymbol * 2);
+		node = stream.readByte();
+		stream.seek(nextbitspos);
+
+		if (node == eofsymbol)
+			break;
+		if (node < eofsymbol) {
+			*luma++ = node;
+			if (luma >= lumaend)
+				break;
+			node = root;
+		}
+		if (!mask) {
+			if (stream.eos())
+				break;
+			bits = stream.readByte();
+			mask = 0x80;
+		}
+	}
+}
+
+void XanDecoder::convertYUVtoRGBSurface() {
+	// Expand luma from 6-bit to 8-bit.
+	for (int i = 0; i < _width * _height; i++)
+		_ybuf[i] = _lumabuf[i] << 2 | _lumabuf[i] >> 4;
+
+	YUVToRGBMan.convert420(&_surface, Graphics::YUVToRGBManager::kScaleFull,
+						   _ybuf, _ubuf, _vbuf, _width, (_height / 2) * 2, _width, _width / 2);
+}
+
+Graphics::PixelFormat XanDecoder::getPixelFormat() const {
+	return Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24);
+}
+
+} // End of namespace Image
diff --git a/image/codecs/xan.h b/image/codecs/xan.h
new file mode 100644
index 0000000000..f935ea094f
--- /dev/null
+++ b/image/codecs/xan.h
@@ -0,0 +1,89 @@
+/* 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 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.
+ *
+ */
+
+#ifndef IMAGE_CODECS_XAN_H
+#define IMAGE_CODECS_XAN_H
+
+#include "image/codecs/codec.h"
+
+namespace Image {
+
+/**
+ * Xan image decoder. (fourcc Xxan)
+ *
+ * Used by Crusader: No Regret AVI files
+ *
+ * This code was created based on the multimedia wiki:
+ * https://wiki.multimedia.cx/index.php/Origin_Xan_Codec
+ * and ffmpeg's libavcodec/xxan.c.
+ * The ffmpeg code is LGPL2 licensed and Copyright (C) 2011
+ * Konstantin Shishkov based on work by Mike Melanson.
+ *
+ * A similar format is used in Wing Commander III (although not in an AVI
+ * container) and IV.
+ */
+class XanDecoder : public Codec {
+public:
+	XanDecoder (int width, int height, int bitsPerPixel);
+	~XanDecoder();
+
+	const Graphics::Surface *decodeFrame(Common::SeekableReadStream &stream) override;
+	Graphics::PixelFormat getPixelFormat() const override;
+
+private:
+	void decodeFrameType0(Common::SeekableReadStream &stream);
+	void decodeFrameType1(Common::SeekableReadStream &stream);
+
+	/** Decompress the huffman table for base luma data */
+	void decompressLuma(Common::SeekableReadStream &stream);
+
+	bool decodeChroma(Common::SeekableReadStream &stream, int chroma_off);
+
+	/** convert the internally expanded YUV to the output RGBA surface */
+	void convertYUVtoRGBSurface();
+
+	/** A buffer to hold the final frame in RGBA */
+	Graphics::Surface _surface;
+
+	/** Dest surface width and height */
+	int _width, _height;
+
+	/** If true, decode chroma vals in Wing Commander 4 style (false = No Regret style) */
+	bool _wc4Mode;
+
+	/** A buffer to hold scratch data.  Either chroma data in progress, or
+	 * decompressed delta luma values (5-bit).  Interpretation depends on frame type. */
+	uint8 *_scratchbuf;
+	/** A buffer to hold expanded/interpolated absolute luma values (6-bit) from the values in _scratchbuf.
+	 * These still need to be multiplied out to make 8-bit values. */
+	uint8 *_lumabuf;
+	/** a buffer for uncompressed and multiplied out "y" values (of yuv) */
+	uint8 *_ybuf;
+	/** a buffer for uncompressed "u" values (of yuv) */
+	uint8 *_ubuf;
+	/** a buffer for uncompressed "v" values (of yuv) */
+	uint8 *_vbuf;
+};
+
+} // End of namespace Image
+
+#endif
diff --git a/image/module.mk b/image/module.mk
index 2100f3093c..23424e5ced 100644
--- a/image/module.mk
+++ b/image/module.mk
@@ -26,6 +26,7 @@ MODULE_OBJS := \
 	codecs/smc.o \
 	codecs/svq1.o \
 	codecs/truemotion1.o \
+	codecs/xan.o \
 	codecs/indeo/indeo.o \
 	codecs/indeo/indeo_dsp.o \
 	codecs/indeo/mem.o \


Commit: a20b8781e1fb6f7531f763d771ff7120d4737045
    https://github.com/scummvm/scummvm/commit/a20b8781e1fb6f7531f763d771ff7120d4737045
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-03-14T19:16:24+01:00

Commit Message:
VIDEO: Add support for Xan codec videos (Crusader, Wing Commander)

Changed paths:
    video/avi_decoder.cpp
    video/avi_decoder.h


diff --git a/video/avi_decoder.cpp b/video/avi_decoder.cpp
index 309f0bc122..0266f8bbe5 100644
--- a/video/avi_decoder.cpp
+++ b/video/avi_decoder.cpp
@@ -34,6 +34,7 @@
 #include "audio/decoders/adpcm.h"
 #include "audio/decoders/mp3.h"
 #include "audio/decoders/raw.h"
+#include "audio/decoders/xan_dpcm.h"
 
 // Video Codecs
 #include "image/codecs/codec.h"
@@ -70,6 +71,11 @@ namespace Video {
 #define ID_PRMI MKTAG('P','R','M','I')
 #define ID_STRN MKTAG('s','t','r','n')
 #define ID_INDX MKTAG('i','n','d','x')
+#define ID_INDX2 MKTAG('I','N','D','X')
+#define ID__PC_ MKTAG('_','P','C','_')
+#define ID_PAL8 MKTAG('P','A','L','8')
+#define ID_BEST MKTAG('B','E','S','T')
+#define ID_SHOT MKTAG('S','H','O','T')
 
 // Stream Types
 enum {
@@ -212,9 +218,16 @@ bool AVIDecoder::parseNextChunk() {
 	case ID_DISP: // Metadata, should be safe to ignore
 	case ID_DMLH: // OpenDML extension, contains an extra total frames field, safe to ignore
 	case ID_INDX: // OpenDML extension, contains another type of index
+	case ID__PC_: // block in Origin Systems Xxan videos
+	case ID_SHOT: // block in Origin Systems Xxan videos
+	case ID_BEST: // block in Origin Systems Xxan videos
+	case ID_INDX2: // block in Origin Systems Xxan videos
 		skipChunk(size);
 		break;
-	case ID_STRN: // Metadata, safe to ignore
+	case ID_PAL8: // PAL8 block in Origin Systems Xxan videos
+		readPalette8(size);
+		break;
+	case ID_STRN:
 		readStreamName(size);
 		break;
 	case ID_IDX1:
@@ -385,6 +398,28 @@ void AVIDecoder::readStreamName(uint32 size) {
 	}
 }
 
+void AVIDecoder::readPalette8(uint32 size) {
+	if (size < 768) {
+		warning("AVI palette8 is too small (%d, expected >= 768)", size);
+		skipChunk(size);
+		return;
+	}
+
+	// Should also be able to load 768 byte palette8 entries here.
+	// Not supported at the moment.
+	if (!_lastAddedTrack || size != 1024) {
+		skipChunk(size);
+	} else {
+		AVIVideoTrack *vidTrack = dynamic_cast<AVIVideoTrack *>(_lastAddedTrack);
+		if (vidTrack) {
+			vidTrack->loadPaletteFromChunkRaw(_fileStream, 0, 256);
+		} else {
+			skipChunk(size);
+			warning("unexpected palette8 on a non-video track");
+		}
+	}
+}
+
 bool AVIDecoder::loadStream(Common::SeekableReadStream *stream) {
 	close();
 
@@ -943,6 +978,19 @@ Graphics::PixelFormat AVIDecoder::AVIVideoTrack::getPixelFormat() const {
 	return Graphics::PixelFormat();
 }
 
+void AVIDecoder::AVIVideoTrack::loadPaletteFromChunkRaw(Common::SeekableReadStream *chunk, int firstEntry, int numEntries) {
+	assert(chunk);
+	assert(firstEntry >= 0);
+	assert(numEntries > 0);
+	for (uint16 i = firstEntry; i < numEntries + firstEntry; i++) {
+		_palette[i * 3] = chunk->readByte();
+		_palette[i * 3 + 1] = chunk->readByte();
+		_palette[i * 3 + 2] = chunk->readByte();
+		chunk->readByte(); // Flags that don't serve us any purpose
+	}
+	_dirtyPalette = true;
+}
+
 void AVIDecoder::AVIVideoTrack::loadPaletteFromChunk(Common::SeekableReadStream *chunk) {
 	assert(chunk);
 	byte firstEntry = chunk->readByte();
@@ -953,17 +1001,12 @@ void AVIDecoder::AVIVideoTrack::loadPaletteFromChunk(Common::SeekableReadStream
 	if (numEntries == 0)
 		numEntries = 256;
 
-	for (uint16 i = firstEntry; i < numEntries + firstEntry; i++) {
-		_palette[i * 3] = chunk->readByte();
-		_palette[i * 3 + 1] = chunk->readByte();
-		_palette[i * 3 + 2] = chunk->readByte();
-		chunk->readByte(); // Flags that don't serve us any purpose
-	}
+	loadPaletteFromChunkRaw(chunk, firstEntry, numEntries);
 
 	delete chunk;
-	_dirtyPalette = true;
 }
 
+
 void AVIDecoder::AVIVideoTrack::useInitialPalette() {
 	_dirtyPalette = false;
 
@@ -1112,6 +1155,9 @@ void AVIDecoder::AVIAudioTrack::createAudioStream() {
 		_packetStream = Audio::makePacketizedRawStream(_wvInfo.samplesPerSec, flags);
 		break;
 	}
+	case Audio::kWaveFormatXanDPCM:
+		_packetStream = new Audio::XanDPCMStream(_wvInfo.samplesPerSec, _wvInfo.channels);
+		break;
 	case Audio::kWaveFormatMSADPCM:
 		_packetStream = Audio::makePacketizedADPCMStream(Audio::kADPCMMS, _wvInfo.samplesPerSec, _wvInfo.channels, _wvInfo.blockAlign);
 		break;
diff --git a/video/avi_decoder.h b/video/avi_decoder.h
index e0fb0094f9..6ec0d285b5 100644
--- a/video/avi_decoder.h
+++ b/video/avi_decoder.h
@@ -221,6 +221,7 @@ protected:
 		bool hasDirtyPalette() const;
 		void setCurFrame(int frame) { _curFrame = frame; }
 		void loadPaletteFromChunk(Common::SeekableReadStream *chunk);
+		void loadPaletteFromChunkRaw(Common::SeekableReadStream *chunk, int firstEntry, int numEntries);
 		void useInitialPalette();
 		bool canDither() const;
 		void setDither(const byte *palette);
@@ -338,6 +339,7 @@ protected:
 	void handleList(uint32 listSize);
 	void handleStreamHeader(uint32 size);
 	void readStreamName(uint32 size);
+	void readPalette8(uint32 size);
 	uint16 getStreamType(uint32 tag) const { return tag & 0xFFFF; }
 	static byte getStreamIndex(uint32 tag);
 	void checkTruemotion1();


Commit: c4baf30f9bd88148a961ba592d8c5120486175e5
    https://github.com/scummvm/scummvm/commit/c4baf30f9bd88148a961ba592d8c5120486175e5
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2021-03-14T19:16:24+01:00

Commit Message:
ULTIMA8: Add Crusader: No Regret movies

Changed paths:
    engines/ultima/ultima8/games/remorse_game.cpp
    engines/ultima/ultima8/games/u8_game.cpp
    engines/ultima/ultima8/graphics/avi_player.cpp
    engines/ultima/ultima8/graphics/avi_player.h
    engines/ultima/ultima8/gumps/movie_gump.cpp
    engines/ultima/ultima8/gumps/movie_gump.h
    engines/ultima/ultima8/gumps/weasel_gump.cpp
    engines/ultima/ultima8/misc/debugger.cpp


diff --git a/engines/ultima/ultima8/games/remorse_game.cpp b/engines/ultima/ultima8/games/remorse_game.cpp
index ec95035d35..5a454fd3f1 100644
--- a/engines/ultima/ultima8/games/remorse_game.cpp
+++ b/engines/ultima/ultima8/games/remorse_game.cpp
@@ -148,7 +148,7 @@ bool RemorseGame::startInitialUsecode(int saveSlot) {
 }
 
 
-static ProcId playMovie(const char *movieID, bool fade) {
+static ProcId playMovie(const char *movieID, bool fade, bool noScale) {
 	const Std::string filename = Std::string::format("flics/%s.avi", movieID);
 	FileSystem *filesys = FileSystem::get_instance();
 	Common::SeekableReadStream *rs = filesys->ReadFile(filename);
@@ -157,20 +157,21 @@ static ProcId playMovie(const char *movieID, bool fade) {
 		return 0;
 	}
 	// TODO: Add support for subtitles (.txt file).  The format is very simple.
-	return MovieGump::U8MovieViewer(rs, fade);
+	return MovieGump::U8MovieViewer(rs, fade, false, noScale);
 }
 
 ProcId RemorseGame::playIntroMovie(bool fade) {
-	return playMovie("T01", fade);
+	const char *name = (GAME_IS_REMORSE ? "T01" : "origin");
+	return playMovie(name, fade, true);
 }
 
 ProcId RemorseGame::playIntroMovie2(bool fade) {
-	return playMovie("T02", fade);
+	const char *name = (GAME_IS_REMORSE ? "T02" : "ANIM01");
+	return playMovie(name, fade, false);
 }
 
-
 ProcId RemorseGame::playEndgameMovie(bool fade) {
-	return playMovie("O01", fade);
+	return playMovie("O01", fade, false);
 }
 
 void RemorseGame::playCredits() {
@@ -178,44 +179,6 @@ void RemorseGame::playCredits() {
 }
 
 void RemorseGame::writeSaveInfo(Common::WriteStream *ws) {
-#if 0
-	MainActor *av = getMainActor();
-	int32 x, y, z;
-
-	const Std::string &avname = av->getName();
-	uint8 namelength = static_cast<uint8>(avname.size());
-	ws->writeByte(namelength);
-	for (unsigned int i = 0; i < namelength; ++i)
-		ws->writeByte(static_cast<uint8>(avname[i]));
-
-	av->getLocation(x, y, z);
-	ws->writeUint16LE(av->getMapNum());
-	ws->writeUint32LE(static_cast<uint32>(x));
-	ws->writeUint32LE(static_cast<uint32>(y));
-	ws->writeUint32LE(static_cast<uint32>(z));
-
-	ws->writeUint16LE(av->getStr());
-	ws->writeUint16LE(av->getInt());
-	ws->writeUint16LE(av->getDex());
-	ws->writeUint16LE(av->getHP());
-	ws->writeUint16LE(av->getMaxHP());
-	ws->writeUint16LE(av->getMana());
-	ws->writeUint16LE(av->getMaxMana());
-	ws->writeUint16LE(av->getArmourClass());
-	ws->writeUint16LE(av->getTotalWeight());
-
-	for (unsigned int i = 1; i <= 6; i++) {
-		const uint16 objid = av->getEquip(i);
-		const Item *item = getItem(objid);
-		if (item) {
-			ws->writeUint32LE(item->getShape());
-			ws->writeUint32LE(item->getFrame());
-		} else {
-			ws->writeUint32LE(0);
-			ws->writeUint32LE(0);
-		}
-	}
-#endif
 }
 
 } // End of namespace Ultima8
diff --git a/engines/ultima/ultima8/games/u8_game.cpp b/engines/ultima/ultima8/games/u8_game.cpp
index 467b604298..a8878748c5 100644
--- a/engines/ultima/ultima8/games/u8_game.cpp
+++ b/engines/ultima/ultima8/games/u8_game.cpp
@@ -174,7 +174,7 @@ ProcId U8Game::playIntroMovie(bool fade) {
 		return 0;
 	}
 
-	return MovieGump::U8MovieViewer(skf, fade, true);
+	return MovieGump::U8MovieViewer(skf, fade, true, true);
 }
 
 ProcId U8Game::playEndgameMovie(bool fade) {
@@ -186,7 +186,7 @@ ProcId U8Game::playEndgameMovie(bool fade) {
 		return 0;
 	}
 
-	return MovieGump::U8MovieViewer(skf, fade);
+	return MovieGump::U8MovieViewer(skf, fade, false, true);
 }
 
 void U8Game::playCredits() {
diff --git a/engines/ultima/ultima8/graphics/avi_player.cpp b/engines/ultima/ultima8/graphics/avi_player.cpp
index ae5e958715..8469dad12f 100644
--- a/engines/ultima/ultima8/graphics/avi_player.cpp
+++ b/engines/ultima/ultima8/graphics/avi_player.cpp
@@ -28,14 +28,14 @@
 namespace Ultima {
 namespace Ultima8 {
 
-AVIPlayer::AVIPlayer(Common::SeekableReadStream *rs, int width, int height, const byte *overridePal)
+AVIPlayer::AVIPlayer(Common::SeekableReadStream *rs, int width, int height, const byte *overridePal, bool noScale)
 	: MoviePlayer(), _playing(false), _width(width), _height(height),
 	  _doubleSize(false), _pausedMusic(false), _overridePal(overridePal) {
 	_decoder = new Video::AVIDecoder();
 	_decoder->loadStream(rs);
 	uint32 vidWidth = _decoder->getWidth();
 	uint32 vidHeight = _decoder->getHeight();
-	if (vidWidth <= _width / 2 && vidHeight <= _height / 2) {
+	if (vidWidth <= _width / 2 && vidHeight <= _height / 2 && !noScale) {
 		_doubleSize = true;
 		vidHeight *= 2;
 		vidWidth *= 2;
@@ -95,14 +95,18 @@ void AVIPlayer::paint(RenderSurface *surf, int /*lerp*/) {
 			_currentFrame.setPalette(pal, 0, 256);
 		}
 		if (_doubleSize) {
+			// TODO: Add support for multiple bytes per pixel
 			assert(_currentFrame.w == frame->w * 2 && _currentFrame.h == frame->h * 2);
+			const int bpp = frame->format.bytesPerPixel;
 			for (int y = 0; y < frame->h; y++) {
 				const uint8 *srcPixel = static_cast<const uint8 *>(frame->getPixels()) + frame->pitch * y;
 				uint8 *dstPixels = static_cast<uint8 *>(_currentFrame.getPixels()) + _currentFrame.pitch * y * 2;
 				for (int x = 0; x < frame->w; x++) {
-					dstPixels[x * 2] = *srcPixel;
-					dstPixels[x * 2 + 1] = *srcPixel;
-					srcPixel++;
+					for (int i = 0; i < bpp; i++) {
+						dstPixels[x * 2 * bpp + i] = *srcPixel;
+						dstPixels[x * 2 * bpp + i + bpp] = *srcPixel;
+						srcPixel++;
+					}
 				}
 			}
 		} else {
diff --git a/engines/ultima/ultima8/graphics/avi_player.h b/engines/ultima/ultima8/graphics/avi_player.h
index fe5eb638d4..4d5e663947 100644
--- a/engines/ultima/ultima8/graphics/avi_player.h
+++ b/engines/ultima/ultima8/graphics/avi_player.h
@@ -35,7 +35,13 @@ namespace Ultima8 {
 
 class AVIPlayer : public MoviePlayer {
 public:
-	AVIPlayer(Common::SeekableReadStream *rs, int width, int height, const byte *overridePal);
+	//!
+	//! Create a new player for the given stream at the given size.  Playback is
+	//! automatically scaled up using the line-skip style from Crusader, unless
+	//! noScale flag is set.
+	//! If overridePal is set, use that as the palette instead of the AVI's one.
+	//!
+	AVIPlayer(Common::SeekableReadStream *rs, int width, int height, const byte *overridePal, bool noScale);
 	~AVIPlayer();
 
 	void run() override;
diff --git a/engines/ultima/ultima8/gumps/movie_gump.cpp b/engines/ultima/ultima8/gumps/movie_gump.cpp
index f7b95c40bc..c77705fc61 100644
--- a/engines/ultima/ultima8/gumps/movie_gump.cpp
+++ b/engines/ultima/ultima8/gumps/movie_gump.cpp
@@ -46,13 +46,13 @@ MovieGump::MovieGump() : ModalGump(), _player(nullptr) {
 }
 
 MovieGump::MovieGump(int width, int height, Common::SeekableReadStream *rs,
-                     bool introMusicHack, const byte *overridePal,
+                     bool introMusicHack, bool noScale, const byte *overridePal,
 					 uint32 flags, int32 layer)
 		: ModalGump(50, 50, width, height, 0, flags, layer) {
 	uint32 stream_id = rs->readUint32BE();
 	rs->seek(-4, SEEK_CUR);
 	if (stream_id == 0x52494646) {// 'RIFF' - crusader AVIs
-		_player = new AVIPlayer(rs, width, height, overridePal);
+		_player = new AVIPlayer(rs, width, height, overridePal, noScale);
 	} else {
 		_player = new SKFPlayer(rs, width, height, introMusicHack);
 	}
@@ -116,12 +116,12 @@ bool MovieGump::OnKeyDown(int key, int mod) {
 }
 
 //static
-ProcId MovieGump::U8MovieViewer(Common::SeekableReadStream *rs, bool fade, bool introMusicHack) {
+ProcId MovieGump::U8MovieViewer(Common::SeekableReadStream *rs, bool fade, bool introMusicHack, bool noScale) {
 	ModalGump *gump;
 	if (GAME_IS_U8)
-		gump = new MovieGump(320, 200, rs, introMusicHack);
+		gump = new MovieGump(320, 200, rs, introMusicHack, noScale);
 	else
-		gump = new MovieGump(640, 480, rs, introMusicHack);
+		gump = new MovieGump(640, 480, rs, introMusicHack, noScale);
 
 	if (fade) {
 		FadeToModalProcess *p = new FadeToModalProcess(gump);
@@ -203,7 +203,7 @@ uint32 MovieGump::I_playMovieOverlay(const uint8 *args,
 
 		Common::SeekableReadStream *rs = _tryLoadCruMovie(name);
 		if (rs) {
-			Gump *gump = new MovieGump(x, y, rs, false, pal->_palette);
+			Gump *gump = new MovieGump(x, y, rs, false, false, pal->_palette);
 			gump->InitGump(nullptr, true);
 			gump->setRelativePosition(CENTER);
 		}
@@ -221,7 +221,7 @@ uint32 MovieGump::I_playMovieCutscene(const uint8 *args, unsigned int /*argsize*
 	if (item) {
 		Common::SeekableReadStream *rs = _tryLoadCruMovie(name);
 		if (rs) {
-			Gump *gump = new MovieGump(x * 3, y * 3, rs, false);
+			Gump *gump = new MovieGump(x * 3, y * 3, rs, false, false);
 			gump->InitGump(nullptr, true);
 			gump->setRelativePosition(CENTER);
 		}
@@ -241,7 +241,7 @@ uint32 MovieGump::I_playMovieCutsceneAlt(const uint8 *args, unsigned int /*argsi
 	if (item) {
 		Common::SeekableReadStream *rs = _tryLoadCruMovie(name);
 		if (rs) {
-			Gump *gump = new MovieGump(x * 3, y * 3, rs, false);
+			Gump *gump = new MovieGump(x * 3, y * 3, rs, false, false);
 			gump->InitGump(nullptr, true);
 			gump->setRelativePosition(CENTER);
 		}
diff --git a/engines/ultima/ultima8/gumps/movie_gump.h b/engines/ultima/ultima8/gumps/movie_gump.h
index fc90558a63..ee00741710 100644
--- a/engines/ultima/ultima8/gumps/movie_gump.h
+++ b/engines/ultima/ultima8/gumps/movie_gump.h
@@ -39,7 +39,8 @@ public:
 
 	MovieGump();
 	MovieGump(int width, int height, Common::SeekableReadStream *rs,
-			  bool introMusicHack = false, const byte *overridePal = nullptr,
+			  bool introMusicHack = false, bool noScale = false,
+			  const byte *overridePal = nullptr,
 	          uint32 flags = FLAG_PREVENT_SAVE, int32 layer = LAYER_MODAL);
 	~MovieGump() override;
 
@@ -54,7 +55,7 @@ public:
 
 	bool OnKeyDown(int key, int mod) override;
 
-	static ProcId U8MovieViewer(Common::SeekableReadStream *rs, bool fade, bool introMusicHack = false);
+	static ProcId U8MovieViewer(Common::SeekableReadStream *rs, bool fade, bool introMusicHack, bool noScale);
 
 	bool loadData(Common::ReadStream *rs);
 	void saveData(Common::WriteStream *ws) override;
diff --git a/engines/ultima/ultima8/gumps/weasel_gump.cpp b/engines/ultima/ultima8/gumps/weasel_gump.cpp
index f105b6d873..deeec08df3 100644
--- a/engines/ultima/ultima8/gumps/weasel_gump.cpp
+++ b/engines/ultima/ultima8/gumps/weasel_gump.cpp
@@ -206,7 +206,7 @@ Gump *WeaselGump::playMovie(const Std::string &filename) {
 		warning("Couldn't load flic %s", filename.c_str());
 		return nullptr;
 	}
-	Gump *gump = new MovieGump(600, 450, rs, false);
+	Gump *gump = new MovieGump(600, 450, rs, false, false);
 	gump->InitGump(this, true);
 	gump->setRelativePosition(CENTER);
 	gump->CreateNotifier();
diff --git a/engines/ultima/ultima8/misc/debugger.cpp b/engines/ultima/ultima8/misc/debugger.cpp
index a66656859e..d64dd48cc7 100644
--- a/engines/ultima/ultima8/misc/debugger.cpp
+++ b/engines/ultima/ultima8/misc/debugger.cpp
@@ -1759,7 +1759,7 @@ bool Debugger::cmdPlayMovie(int argc, const char **argv) {
 		return true;
 	}
 
-	MovieGump::U8MovieViewer(skf, false);
+	MovieGump::U8MovieViewer(skf, false, false, true);
 	return false;
 }
 




More information about the Scummvm-git-logs mailing list