[Scummvm-git-logs] scummvm master -> 8cb29bd921b65e4495a07e8c46eff3e616d3fca3

sev- noreply at scummvm.org
Sat Apr 29 12:20:19 UTC 2023


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

Summary:
88ba80555a DIRECTOR: Add initial support for film loops in D3
ff959da626 DIRECTOR: LINGO: Fix consts table loading for D5
459115fb78 DIRECTOR: Fix castInfo loading for D5
cc353d9acf DIRECTOR: Fix BitmapCastMember for D5
1d84409629 DIRECTOR: Fix ScriptCastMember for D5
b2c0255912 DIRECTOR: Improve frame parser for D5
7b8fee4599 DIRECTOR: Add stubs for V5 cast library info loader
f0343d6f02 DIRECTOR: Add stubs for BatQT XObj
9ff4641c68 DIRECTOR: LINGO: Add RECT support to getObjectProp
f4cbadaca2 DIRECTOR: XOBJ: FindSys must return a path with a slash
a407f165a6 DIRECTOR: XOBJ: Remove directory from fileio paths
4e1b32c0d4 DIRECTOR: Add filesystem quirk for teamxtreme2
8ff91ce024 DIRECTOR: LINGO: Make b_getNthFileNameInFolder always check cache
ef3d360552 DIRECTOR: Unify arguments to Cast::loadXData functions
f703ee6ecf DIRECTOR: Split CastMember classes into files
e289ea5896 DIRECTOR: Remove shared cast check from Cast::loadBitmapData()
1fd0692f6b DIRECTOR: Move load operations inside cast member
4557c52258 DIRECTOR: Lazily load cast member data from archive
14af094515 DIRECTOR: XOBJ: Replace stub boilerplate with macro
2730ccc7ed DIRECTOR: LINGO: Make arithmetic issues invoke lingoError()
8cb29bd921 DIRECTOR: LINGO: Add special override mechanics for List builtins


Commit: 88ba80555aa268db16118ba303a7306f156e7993
    https://github.com/scummvm/scummvm/commit/88ba80555aa268db16118ba303a7306f156e7993
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: Add initial support for film loops in D3

Changed paths:
    engines/director/cast.cpp
    engines/director/castmember.cpp
    engines/director/castmember.h


diff --git a/engines/director/cast.cpp b/engines/director/cast.cpp
index 8461c6c623c..8e586c20466 100644
--- a/engines/director/cast.cpp
+++ b/engines/director/cast.cpp
@@ -623,7 +623,7 @@ void Cast::loadPaletteData(PaletteCastMember *member) {
 		paletteId = member->_children[0].index;
 	} else if (_version < kFileVer400) {
 		// For D3 and below, palette IDs are stored in the CLUT resource as cast ID + 1024
-		paletteId = member->getID() + 1024;
+		paletteId = member->getID() + _castIDoffset;
 	} else {
 		warning("Cast::loadPaletteData(): Expected 1 child for palette cast, got %d", member->_children.size());
 	}
@@ -634,14 +634,22 @@ void Cast::loadPaletteData(PaletteCastMember *member) {
 }
 
 void Cast::loadFilmLoopData(FilmLoopCastMember *member) {
-	if (_version >= kFileVer400 && _version < kFileVer500) {
+	if (_version < kFileVer400) {
+		// Director 3 and below should have a SCVW resource
+		uint16 filmLoopId = member->getID() + _castIDoffset;
+		uint32 tag = MKTAG('S', 'C', 'V', 'W');
+		Common::SeekableReadStreamEndian *loop = _castArchive->getResource(tag, filmLoopId);
+		debugC(2, kDebugLoading, "****** Loading '%s' id: %d, %d bytes", tag2str(tag), filmLoopId, (int)loop->size());
+		member->loadFilmLoopData(*loop);
+		delete loop;
+	} else if (_version >= kFileVer400 && _version < kFileVer500) {
 		if (member->_children.size() == 1) {
 			uint16 filmLoopId = member->_children[0].index;
 			uint32 tag = member->_children[0].tag;
 			if (_castArchive->hasResource(tag, filmLoopId)) {
 				Common::SeekableReadStreamEndian *loop = _castArchive->getResource(tag, filmLoopId);
 				debugC(2, kDebugLoading, "****** Loading '%s' id: %d, %d bytes", tag2str(tag), filmLoopId, (int)loop->size());
-				member->loadFilmLoopData(*loop);
+				member->loadFilmLoopDataV4(*loop);
 				delete loop;
 			} else {
 				warning("Cast::loadFilmLoopData(): Film loop not found");
@@ -1023,6 +1031,10 @@ void Cast::loadCastDataVWCR(Common::SeekableReadStreamEndian &stream) {
 			debugC(3, kDebugLoading, "Cast::loadCastDataVWCR(): CastTypes id: %d(%s) PaletteCastMember", id, numToCastNum(id));
 			_loadedCast->setVal(id, new PaletteCastMember(this, id, stream, _version));
 			break;
+		case kCastFilmLoop:
+			debugC(3, kDebugLoading, "Cast::loadCastDataVWCR(): CastTypes id: %d(%s) FilmLoopCastMember", id, numToCastNum(id));
+			_loadedCast->setVal(id, new FilmLoopCastMember(this, id, stream, _version));
+			break;
 		default:
 			warning("Cast::loadCastDataVWCR(): Unhandled cast id: %d(%s), type: %d, %d bytes", id, numToCastNum(id), castType, size);
 			break;
@@ -1603,8 +1615,12 @@ Common::String Cast::formatCastSummary(int castId = -1) {
 		CastMember *castMember = getCastMember(*it);
 		CastMemberInfo *castMemberInfo = getCastMemberInfo(*it);
 		Common::String info = castMember->formatInfo();
-		result += Common::String::format("%5d: type=%s, name=\"%s\"",
-			*it, castType2str(castMember->_type),
+		result += Common::String::format("%5d", *it);
+		if (_version < kFileVer400) {
+			result += Common::String::format(" (%s)", numToCastNum(*it));
+		}
+		result += Common::String::format(": type=%s, name=\"%s\"",
+			castType2str(castMember->_type),
 			castMemberInfo ? castMemberInfo->name.c_str() : ""
 		);
 
diff --git a/engines/director/castmember.cpp b/engines/director/castmember.cpp
index a60a2bfd4f7..0794cb8eda8 100644
--- a/engines/director/castmember.cpp
+++ b/engines/director/castmember.cpp
@@ -995,6 +995,137 @@ void FilmLoopCastMember::loadFilmLoopData(Common::SeekableReadStreamEndian &stre
 	_initialRect = Common::Rect();
 	_frames.clear();
 
+	uint32 size = stream.readUint32BE();
+	if (debugChannelSet(5, kDebugLoading)) {
+		debugC(5, kDebugLoading, "SCVW body:");
+		uint32 pos = stream.pos();
+		stream.seek(0);
+		stream.hexdump(size);
+		stream.seek(pos);
+	}
+	uint16 channelSize = 16;
+	FilmLoopFrame newFrame;
+
+	while (stream.pos() < size) {
+		uint16 frameSize = stream.readUint16BE() - 2;
+		if (debugChannelSet(5, kDebugLoading)) {
+			debugC(5, kDebugLoading, "Frame entry:");
+			stream.hexdump(frameSize);
+		}
+
+		while (frameSize > 0) {
+			int msgWidth = stream.readByte() * 2;
+			int order = stream.readByte() * 2 - 0x20;
+			frameSize -= 2;
+			debugC(8, kDebugLoading, "Message: msgWidth %d, order %d", msgWidth, order);
+			if (debugChannelSet(8, kDebugLoading)) {
+				stream.hexdump(msgWidth);
+			}
+
+			int fieldPosition = order;
+			int finishPosition = order + msgWidth;
+			while (fieldPosition < finishPosition) {
+				int channel = (fieldPosition / channelSize);
+				int channelOffset = fieldPosition % channelSize;
+
+				Sprite sprite(nullptr);
+				sprite._movie = g_director->getCurrentMovie();
+				if (newFrame.sprites.contains(channel)) {
+					sprite = newFrame.sprites.getVal(channel);
+				}
+				sprite._spriteType = kCastMemberSprite;
+				sprite._puppet = 1;
+				sprite._stretch = 1;
+
+				switch (channelOffset) {
+				case kSpritePositionUnk1:
+					stream.readByte();
+					fieldPosition++;
+					break;
+				case kSpritePositionEnabled:
+					sprite._enabled = stream.readByte() != 0;
+					fieldPosition++;
+					break;
+				case kSpritePositionUnk2:
+					stream.readUint16BE();
+					fieldPosition += 2;
+					break;
+				case kSpritePositionFlags:
+					sprite._thickness = stream.readByte();
+					sprite._inkData = stream.readByte();
+					sprite._ink = static_cast<InkType>(sprite._inkData & 0x3f);
+
+					if (sprite._inkData & 0x40)
+						sprite._trails = 1;
+					else
+						sprite._trails = 0;
+
+					fieldPosition += 2;
+					break;
+				case kSpritePositionCastId:
+					sprite.setCast(CastMemberID(stream.readUint16(), 0));
+					fieldPosition += 2;
+					break;
+				case kSpritePositionY:
+					sprite._startPoint.y = stream.readUint16();
+					fieldPosition += 2;
+					break;
+				case kSpritePositionX:
+					sprite._startPoint.x = stream.readUint16();
+					fieldPosition += 2;
+					break;
+				case kSpritePositionWidth:
+					sprite._width = stream.readUint16();
+					fieldPosition += 2;
+					break;
+				case kSpritePositionHeight:
+					sprite._height = stream.readUint16();
+					fieldPosition += 2;
+					break;
+				default:
+					stream.readUint16BE();
+					fieldPosition += 2;
+					break;
+				}
+				newFrame.sprites.setVal(channel, sprite);
+			}
+
+			frameSize -= msgWidth;
+		}
+
+		for (Common::HashMap<int, Sprite>::iterator s = newFrame.sprites.begin(); s != newFrame.sprites.end(); ++s) {
+			debugC(5, kDebugLoading, "Sprite: channel %d, castId %s, bbox %d %d %d %d", s->_key,
+					s->_value._castId.asString().c_str(), s->_value._startPoint.x, s->_value._startPoint.y,
+					s->_value._width, s->_value._height);
+
+			Common::Point topLeft = s->_value._startPoint + s->_value.getRegistrationOffset();
+			Common::Rect spriteBbox(
+				topLeft.x,
+				topLeft.y,
+				topLeft.x + s->_value._width,
+				topLeft.y + s->_value._height
+			);
+			if (!((spriteBbox.width() == 0) && (spriteBbox.height() == 0))) {
+				if ((_initialRect.width() == 0) && (_initialRect.height() == 0)) {
+					_initialRect = spriteBbox;
+				} else {
+					_initialRect.extend(spriteBbox);
+				}
+			}
+			debugC(8, kDebugLoading, "New bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
+
+		}
+
+		_frames.push_back(newFrame);
+
+	}
+	debugC(5, kDebugLoading, "Full bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
+}
+
+void FilmLoopCastMember::loadFilmLoopDataV4(Common::SeekableReadStreamEndian &stream) {
+	_initialRect = Common::Rect();
+	_frames.clear();
+
 	uint32 size = stream.readUint32BE();
 	if (debugChannelSet(5, kDebugLoading)) {
 		debugC(5, kDebugLoading, "SCVW body:");
diff --git a/engines/director/castmember.h b/engines/director/castmember.h
index 1f38c9be2cf..18fd6b99039 100644
--- a/engines/director/castmember.h
+++ b/engines/director/castmember.h
@@ -228,6 +228,7 @@ public:
 	Common::Array<Channel> *getSubChannels(Common::Rect &bbox, Channel *channel);
 
 	void loadFilmLoopData(Common::SeekableReadStreamEndian &stream);
+	void loadFilmLoopDataV4(Common::SeekableReadStreamEndian &stream);
 
 	Common::String formatInfo() override;
 


Commit: ff959da62627e14b41debe2a96ef4929d8592eea
    https://github.com/scummvm/scummvm/commit/ff959da62627e14b41debe2a96ef4929d8592eea
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: LINGO: Fix consts table loading for D5

Changed paths:
    engines/director/lingo/lingo-bytecode.cpp


diff --git a/engines/director/lingo/lingo-bytecode.cpp b/engines/director/lingo/lingo-bytecode.cpp
index 88616da98fb..adb6b30e540 100644
--- a/engines/director/lingo/lingo-bytecode.cpp
+++ b/engines/director/lingo/lingo-bytecode.cpp
@@ -1146,6 +1146,11 @@ ScriptContext *LingoCompiler::compileLingoV4(Common::SeekableReadStreamEndian &s
 
 	// read each entry in the reference table.
 	stream.seek(constsOffset);
+	int constsIndexSize = MAX((int)constsStoreOffset - (int)constsOffset, 0);
+	if (debugChannelSet(5, kDebugLoading)) {
+		debugC(5, kDebugLoading, "Lscr consts index:");
+		stream.hexdump(constsIndexSize);
+	}
 	for (uint16 i = 0; i < constsCount; i++) {
 		Datum constant;
 		uint32 constType = 0;
@@ -1154,6 +1159,7 @@ ScriptContext *LingoCompiler::compileLingoV4(Common::SeekableReadStreamEndian &s
 		} else {
 			constType = (uint32)stream.readUint16();
 		}
+
 		uint32 value = stream.readUint32();
 		switch (constType) {
 		case 1: // String type
@@ -1225,7 +1231,6 @@ ScriptContext *LingoCompiler::compileLingoV4(Common::SeekableReadStreamEndian &s
 			warning("Unknown constant type %d", constType);
 			break;
 		}
-
 		_assemblyContext->_constants.push_back(constant);
 	}
 	free(constsStore);
@@ -1368,15 +1373,27 @@ ScriptContext *LingoCompiler::compileLingoV4(Common::SeekableReadStreamEndian &s
 			Common::hexdump(codeStore, length, 16, startOffset);
 		}
 
-		uint16 pointer = startOffset - codeStoreOffset;
+		uint32 pointer = startOffset - codeStoreOffset;
 		Common::Array<uint32> offsetList;
 		Common::Array<uint32> jumpList;
+
+		// Size of an entry in the consts index.
+		int constEntrySize = 0;
+		if (version >= kFileVer500) {
+			// For V5 this is uint32 type + uint32 offset
+			constEntrySize = 8;
+		} else {
+			// For V4 this is uint16 type + uint32 offset
+			constEntrySize = 6;
+		}
+
 		while (pointer < startOffset + length - codeStoreOffset) {
 			uint8 opcode = codeStore[pointer];
 			pointer += 1;
 
 			if (opcode == 0x44 || opcode == 0x84) {
-				// push a constant
+				// Opcode for pushing a value from the constants table.
+				// Rewrite these to inline the constant into our bytecode.
 				offsetList.push_back(_currentAssembly->size());
 				int arg = 0;
 				if (opcode == 0x84) {
@@ -1386,11 +1403,12 @@ ScriptContext *LingoCompiler::compileLingoV4(Common::SeekableReadStreamEndian &s
 					arg = (uint8)codeStore[pointer];
 					pointer += 1;
 				}
-				// remove struct size alignment
-				if (arg % 6) {
-					warning("Opcode 0x%02x arg %d not a multiple of 6!", opcode, arg);
+				// The argument is a byte offset to an entry in the consts index.
+				// As such, it should be an exact multiple of the entry size.
+				if (arg % constEntrySize) {
+					warning("Opcode 0x%02x arg %d not a multiple of %d", opcode, arg, constEntrySize);
 				}
-				arg /= 6;
+				arg /= constEntrySize;
 				Datum constant = _assemblyContext->_constants[arg];
 				switch (constant.type) {
 				case INT:
@@ -1468,10 +1486,10 @@ ScriptContext *LingoCompiler::compileLingoV4(Common::SeekableReadStreamEndian &s
 							break;
 						case 'p':
 							// argument is some kind of denormalised offset
-							if (arg % 6) {
-								warning("Argument %d was expected to be a multiple of 6", arg);
+							if (arg % constEntrySize) {
+								warning("Argument %d was expected to be a multiple of %d", arg, constEntrySize);
 							}
-							arg /= 6;
+							arg /= constEntrySize;
 							break;
 						case 'a':
 							// argument is a function argument ID


Commit: 459115fb7855cfe10c6db3273cc1be0045b21254
    https://github.com/scummvm/scummvm/commit/459115fb7855cfe10c6db3273cc1be0045b21254
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: Fix castInfo loading for D5

Changed paths:
    engines/director/archive.cpp
    engines/director/cast.cpp


diff --git a/engines/director/archive.cpp b/engines/director/archive.cpp
index d7131bb12f1..7f163c71b51 100644
--- a/engines/director/archive.cpp
+++ b/engines/director/archive.cpp
@@ -339,7 +339,7 @@ void MacArchive::readTags() {
 			res.name = _resFork->getResName(tagArray[i], idArray[j]);
 			res.tag = tagArray[i];
 			res.index = idArray[j];
-			debug(3, "Found MacArchive resource '%s' %d: %s", tag2str(tagArray[i]), idArray[j], res.name.c_str());
+			debug(3, "MacArchive::readTags(): Found MacArchive resource '%s' %d: %s", tag2str(tagArray[i]), idArray[j], res.name.c_str());
 			if (ConfMan.getBool("dump_scripts"))
 				dumpChunk(res, out);
 		}
@@ -349,6 +349,12 @@ void MacArchive::readTags() {
 			 _types[tagArray[i]] = resMap;
 		}
 	}
+	if (debugChannelSet(5, kDebugLoading)) {
+		debugC(5, kDebugLoading, "MacArchive::readTags(): Resources found:");
+		for (const auto &it : _types) {
+			debugC(5, kDebugLoading, "%s: %d", tag2str(it._key), it._value.size());
+		}
+	}
 }
 
 Common::SeekableReadStreamEndian *MacArchive::getResource(uint32 tag, uint16 id) {
@@ -449,6 +455,12 @@ bool RIFFArchive::openStream(Common::SeekableReadStream *stream, uint32 startOff
 		stream->seek(startResPos);
 	}
 
+	if (debugChannelSet(5, kDebugLoading)) {
+		debugC(5, kDebugLoading, "RIFFArchive::openStream(): Resources found:");
+		for (const auto &it : _types) {
+			debugC(5, kDebugLoading, "%s: %d", tag2str(it._key), it._value.size());
+		}
+	}
 	return true;
 }
 
@@ -741,6 +753,13 @@ bool RIFXArchive::readMemoryMap(Common::SeekableReadStreamEndian &stream, uint32
 		_resources.push_back(&res);
 	}
 
+	if (debugChannelSet(5, kDebugLoading)) {
+		debugC(5, kDebugLoading, "RIFXArchive::readMemoryMap(): Resources found:");
+		for (const auto &it : _types) {
+			debugC(5, kDebugLoading, "%s: %d", tag2str(it._key), it._value.size());
+		}
+	}
+
 	return true;
 }
 
diff --git a/engines/director/cast.cpp b/engines/director/cast.cpp
index 8e586c20466..7d3f905bcf7 100644
--- a/engines/director/cast.cpp
+++ b/engines/director/cast.cpp
@@ -1094,7 +1094,7 @@ void Cast::loadCastData(Common::SeekableReadStreamEndian &stream, uint16 id, Res
 	if (debugChannelSet(5, kDebugLoading) && stream.size() < 2048)
 		stream.hexdump(stream.size());
 
-	uint32 castSize, castInfoSize, size3, castType, castSizeToRead;
+	uint32 castDataSize, castInfoSize,  castType, castDataSizeToRead, castDataOffset, castInfoOffset;
 	byte flags1 = 0, unk1 = 0, unk2 = 0, unk3 = 0;
 
 	// D2-3 cast members should be loaded in loadCastDataVWCR
@@ -1103,7 +1103,6 @@ void Cast::loadCastData(Common::SeekableReadStreamEndian &stream, uint16 id, Res
 		size1 = stream.readUint16();
 		sizeToRead = size1 +16; // 16 is for bounding rects
 		size2 = stream.readUint32();
-		size3 = 0;
 		castType = stream.readByte();
 		unk1 = stream.readByte();
 		unk2 = stream.readByte();
@@ -1112,44 +1111,38 @@ void Cast::loadCastData(Common::SeekableReadStreamEndian &stream, uint16 id, Res
 #endif
 
 	if (_version >= kFileVer400 && _version < kFileVer500) {
-		castSize = stream.readUint16();
-		castSizeToRead = castSize;
+		castDataSize = stream.readUint16();
+		castDataSizeToRead = castDataSize;
 		castInfoSize = stream.readUint32();
-		size3 = 0;
 
 		// these bytes are common but included in cast size
 		castType = stream.readByte();
-		castSizeToRead -= 1;
-		if (castSizeToRead) {
+		castDataSizeToRead -= 1;
+		if (castDataSizeToRead) {
 			flags1 = stream.readByte();
-			castSizeToRead -= 1;
+			castDataSizeToRead -= 1;
 		}
+		castDataOffset = stream.pos();
+		castInfoOffset = stream.pos() + castDataSizeToRead;
 	} else if (_version >= kFileVer500 && _version < kFileVer600) {
 		castType = stream.readUint32();
-		size3 = stream.readUint32();
 		castInfoSize = stream.readUint32();
-		castSize = stream.readUint32();
-		if (castType == 1) {
-			if (size3 == 0)
-				return;
-			for (uint32 skip = 0; skip < (castSize - 4) / 4; skip++)
-				stream.readUint32();
-		}
-
-		castSizeToRead = stream.size();
+		castDataSize = stream.readUint32();
+		castDataSizeToRead = castDataSize;
+		castInfoOffset = stream.pos();
+		castDataOffset = stream.pos() + castInfoSize;
 	} else {
 		error("Cast::loadCastData: unsupported Director version (%d)", _version);
 	}
 
-	debugC(3, kDebugLoading, "Cast::loadCastData(): CASt: id: %d type: %x castSize: %d castInfoSize: %d (%x) size3: %d unk1: %d unk2: %d unk3: %d",
-		id, castType, castSize, castInfoSize, castInfoSize, size3, unk1, unk2, unk3);
+	debugC(3, kDebugLoading, "Cast::loadCastData(): CASt: id: %d type: %x castDataSize: %d castInfoSize: %d (%x) unk1: %d unk2: %d unk3: %d",
+		id, castType, castDataSize, castInfoSize, castInfoSize, unk1, unk2, unk3);
 
 	// read the cast member itself
-
-	byte *data = (byte *)calloc(castSizeToRead, 1);
-	stream.read(data, castSizeToRead);
-
-	Common::MemoryReadStreamEndian castStream(data, castSizeToRead, stream.isBE());
+	byte *data = (byte *)calloc(castDataSizeToRead, 1);
+	stream.seek(castDataOffset);
+	stream.read(data, castDataSizeToRead);
+	Common::MemoryReadStreamEndian castStream(data, castDataSizeToRead, stream.isBE());
 
 	if (_loadedCast->contains(id)) {
 		warning("Cast::loadCastData(): Multiple cast members with ID %d, overwriting", id);
@@ -1225,13 +1218,14 @@ void Cast::loadCastData(Common::SeekableReadStreamEndian &stream, uint16 id, Res
 	free(data);
 
 	// read the cast member info
-
-	if (castInfoSize && _version < kFileVer500) {
-		loadCastInfo(stream, id);
+	if (castInfoSize) {
+		data = (byte *)calloc(castInfoSize, 1);
+		stream.seek(castInfoOffset);
+		stream.read(data, castInfoSize);
+		Common::MemoryReadStreamEndian castInfoStream(data, castInfoSize, stream.isBE());
+		loadCastInfo(castInfoStream, id);
+		free(data);
 	}
-
-	if (size3)
-		warning("Cast::loadCastData(): size3: %x", size3);
 }
 
 struct LingoContextEntry {


Commit: cc353d9acf082a270b87dfde9bda88c71ef47f94
    https://github.com/scummvm/scummvm/commit/cc353d9acf082a270b87dfde9bda88c71ef47f94
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: Fix BitmapCastMember for D5

Changed paths:
    engines/director/castmember.cpp


diff --git a/engines/director/castmember.cpp b/engines/director/castmember.cpp
index 0794cb8eda8..9a9dce3a874 100644
--- a/engines/director/castmember.cpp
+++ b/engines/director/castmember.cpp
@@ -134,7 +134,7 @@ BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, Common::SeekableRe
 		_pitch *= _bitsPerPixel;
 		_pitch >>= 3;
 
-	} else if (version >= kFileVer400 && version < kFileVer500) {
+	} else if (version >= kFileVer400 && version < kFileVer600) {
 		_flags1 = flags1;
 		_pitch = stream.readUint16();
 		_pitch &= 0x0fff;
@@ -149,6 +149,9 @@ BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, Common::SeekableRe
 		if (stream.eos()) {
 			_bitsPerPixel = 0;
 		} else {
+			if (version >= kFileVer500) {
+				stream.readSint16(); // ff ff
+			}
 			_clut = stream.readSint16();
 			if (_clut <= 0) // builtin palette
 				_clut -= 1;
@@ -182,24 +185,6 @@ BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, Common::SeekableRe
 			debug("BitmapCastMember: tail");
 			Common::hexdump(buf, tail);
 		}
-	} else if (version >= kFileVer500) {
-		uint16 count = stream.readUint16();
-		for (uint16 cc = 0; cc < count; cc++)
-			stream.readUint32();
-
-		uint32 stringLength = stream.readUint32();
-		for (uint32 s = 0; s < stringLength; s++)
-			stream.readByte();
-
-		/*uint16 width =*/ stream.readUint16LE(); //maybe?
-		_initialRect = Movie::readRect(stream);
-
-		/*uint32 somethingElse =*/ stream.readUint32();
-		_boundingRect = Movie::readRect(stream);
-
-		_bitsPerPixel = stream.readUint16();
-
-		stream.readUint32();
 	}
 
 	_tag = castTag;


Commit: 1d84409629602847153cb5a1d06d44b58fcc7b24
    https://github.com/scummvm/scummvm/commit/1d84409629602847153cb5a1d06d44b58fcc7b24
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: Fix ScriptCastMember for D5

Changed paths:
    engines/director/castmember.cpp


diff --git a/engines/director/castmember.cpp b/engines/director/castmember.cpp
index 9a9dce3a874..f3d38d47e79 100644
--- a/engines/director/castmember.cpp
+++ b/engines/director/castmember.cpp
@@ -1712,9 +1712,14 @@ ScriptCastMember::ScriptCastMember(Cast *cast, uint16 castId, Common::SeekableRe
 	_type = kCastLingoScript;
 	_scriptType = kNoneScript;
 
+	if (debugChannelSet(5, kDebugLoading)) {
+		debugC(5, kDebugLoading, "ScriptCastMember::ScriptCastMember(): Contents");
+		stream.hexdump(stream.size());
+	}
+
 	if (version < kFileVer400) {
 		error("Unhandled Script cast");
-	} else if (version >= kFileVer400 && version < kFileVer500) {
+	} else if (version >= kFileVer400 && version < kFileVer600) {
 		byte unk1 = stream.readByte();
 		byte type = stream.readByte();
 
@@ -1733,13 +1738,6 @@ ScriptCastMember::ScriptCastMember(Cast *cast, uint16 castId, Common::SeekableRe
 
 		stream.readByte(); // There should be no more data
 		assert(stream.eos());
-	} else if (version >= kFileVer500) {
-		stream.readByte();
-		stream.readByte();
-
-		debugC(4, kDebugLoading, "CASt: Script");
-
-		// WIP need to complete this!
 	}
 }
 


Commit: b2c02559123fdf335003dcdad061b9ac7002d255
    https://github.com/scummvm/scummvm/commit/b2c02559123fdf335003dcdad061b9ac7002d255
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: Improve frame parser for D5

Changed paths:
    engines/director/castmember.cpp
    engines/director/frame.cpp
    engines/director/frame.h
    engines/director/score.cpp


diff --git a/engines/director/castmember.cpp b/engines/director/castmember.cpp
index f3d38d47e79..cc0fd428dbf 100644
--- a/engines/director/castmember.cpp
+++ b/engines/director/castmember.cpp
@@ -150,7 +150,7 @@ BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, Common::SeekableRe
 			_bitsPerPixel = 0;
 		} else {
 			if (version >= kFileVer500) {
-				stream.readSint16(); // ff ff
+				stream.readSint16(); // is this the castlib? was ff ff
 			}
 			_clut = stream.readSint16();
 			if (_clut <= 0) // builtin palette
diff --git a/engines/director/frame.cpp b/engines/director/frame.cpp
index f3d58d92c02..0f27ffe6831 100644
--- a/engines/director/frame.cpp
+++ b/engines/director/frame.cpp
@@ -258,17 +258,47 @@ void Frame::readChannels(Common::SeekableReadStreamEndian *stream, uint16 versio
 			warning("Frame::readChannels(): STUB: unk5: %d 0x%x", unk1, unk1);
 
 	} else if (version >= kFileVer500 && version < kFileVer600) {
+		if (debugChannelSet(8, kDebugLoading)) {
+			debugC(8, kDebugLoading, "Frame::readChannels(): 48 byte header");
+			stream->hexdump(48);
+		}
 		// Sound/Tempo/Transition channel
-		stream->read(unk, 24);
+		uint16 actionCastLib = stream->readUint16();
+		uint16 actionId = stream->readUint16();
+		_actionId = CastMemberID(actionId, actionCastLib);
+		uint16 sound1CastLib = stream->readUint16();
+		uint16 sound1Id = stream->readUint16();
+		_sound1 = CastMemberID(sound1Id, sound1CastLib);
+		uint16 sound2CastLib = stream->readUint16();
+		uint16 sound2Id = stream->readUint16();
+		_sound2 = CastMemberID(sound2Id, sound2CastLib);
+		uint16 transCastLib = stream->readUint16();
+		uint16 transId = stream->readUint16();
+		_trans = CastMemberID(transId, transCastLib);
+
+		stream->read(unk, 5);
+
+		_tempo = stream->readByte();
+
+		stream->read(unk, 2);
 
 		// palette
-		stream->read(unk, 24);
-	} else {
-		// Sound[2]
-		// palette
-		// Transition
-		// Tempo
-		// Script
+		stream->read(unk, 2);
+
+		_palette.paletteId = stream->readSint16();
+		_palette.speed = stream->readByte();
+		_palette.flags = stream->readByte();
+		_palette.colorCycling = (_palette.flags & 0x80) != 0;
+		_palette.normal = (_palette.flags & 0x60) == 0x00;
+		_palette.fadeToBlack = (_palette.flags & 0x60) == 0x60;
+		_palette.fadeToWhite = (_palette.flags & 0x60) == 0x40;
+		_palette.autoReverse = (_palette.flags & 0x10) != 0;
+		_palette.overTime = (_palette.flags & 0x04) != 0;
+		_palette.firstColor = g_director->transformColor(stream->readByte() + 0x80);
+		_palette.lastColor = g_director->transformColor(stream->readByte() + 0x80);
+		_palette.frameCount = stream->readUint16();
+		_palette.cycleCount = stream->readUint16();
+		stream->read(unk, 12);
 	}
 
 	_transChunkSize = CLIP<byte>(_transChunkSize, 0, 128);
diff --git a/engines/director/frame.h b/engines/director/frame.h
index a5b71897d17..3aa51733749 100644
--- a/engines/director/frame.h
+++ b/engines/director/frame.h
@@ -115,6 +115,7 @@ public:
 	uint8 _transArea; // 1 - Whole Window, 0 - Changing Area
 	uint8 _transChunkSize;
 	TransitionType _transType;
+	CastMemberID _trans;
 	PaletteInfo _palette;
 	uint8 _tempo;
 
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index 1c1019656e7..46361104600 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -1292,14 +1292,16 @@ void Score::playQueuedSound() {
 void Score::loadFrames(Common::SeekableReadStreamEndian &stream, uint16 version) {
 	debugC(1, kDebugLoading, "****** Loading frames VWSC");
 
-	//stream.hexdump(stream.size());
+	if (debugChannelSet(8, kDebugLoading)) {
+		stream.hexdump(stream.size());
+	}
 
 	uint32 size = stream.readUint32();
 	size -= 4;
 
 	if (version < kFileVer400) {
 		_numChannelsDisplayed = 30;
-	} else if (version >= kFileVer400 && version < kFileVer500) {
+	} else if (version >= kFileVer400 && version < kFileVer600) {
 		uint32 frame1Offset = stream.readUint32();
 		uint32 numFrames = stream.readUint32();
 		uint16 framesVersion = stream.readUint16();
@@ -1323,38 +1325,6 @@ void Score::loadFrames(Common::SeekableReadStreamEndian &stream, uint16 version)
 		warning("STUB: Score::loadFrames. frame1Offset: %x numFrames: %x version: %x spriteRecordSize: %x numChannels: %x numChannelsDisplayed: %x",
 			frame1Offset, numFrames, framesVersion, spriteRecordSize, numChannels, _numChannelsDisplayed);
 		// Unknown, some bytes - constant (refer to contuinity).
-	} else if (version >= kFileVer500) {
-		//what data is up the top of D5 VWSC?
-		uint32 unk1 = stream.readUint32();
-		uint32 unk2 = stream.readUint32();
-
-		uint16 unk3, unk4, unk5, unk6;
-
-		if (unk2 > 0) {
-			uint32 blockSize = stream.readUint32() - 1;
-			stream.readUint32();
-			stream.readUint32();
-			stream.readUint32();
-			stream.readUint32();
-			for (uint32 skip = 0; skip < blockSize * 4; skip++)
-				stream.readByte();
-
-			//header number two... this is our actual score entry point.
-			unk1 = stream.readUint32();
-			unk2 = stream.readUint32();
-			stream.readUint32();
-			unk3 = stream.readUint16();
-			unk4 = stream.readUint16();
-			unk5 = stream.readUint16();
-			unk6 = stream.readUint16();
-		} else {
-			unk3 = stream.readUint16();
-			unk4 = stream.readUint16();
-			unk5 = stream.readUint16();
-			unk6 = stream.readUint16();
-			size -= 16;
-		}
-		warning("STUB: Score::loadFrames. unk1: %x unk2: %x unk3: %x unk4: %x unk5: %x unk6: %x", unk1, unk2, unk3, unk4, unk5, unk6);
 	}
 
 	uint16 channelSize;
@@ -1378,6 +1348,9 @@ void Score::loadFrames(Common::SeekableReadStreamEndian &stream, uint16 version)
 	while (size != 0 && !stream.eos()) {
 		uint16 frameSize = stream.readUint16();
 		debugC(3, kDebugLoading, "++++++++++ score frame %d (frameSize %d) size %d", _frames.size(), frameSize, size);
+		if (debugChannelSet(8, kDebugLoading)) {
+			stream.hexdump(frameSize);
+		}
 
 		if (frameSize > 0) {
 			Frame *frame = new Frame(this, _numChannelsDisplayed);


Commit: 7b8fee4599bdcc701c4eda6648c7e6a58395f6b7
    https://github.com/scummvm/scummvm/commit/7b8fee4599bdcc701c4eda6648c7e6a58395f6b7
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: Add stubs for V5 cast library info loader

Changed paths:
    engines/director/cast.cpp
    engines/director/cast.h


diff --git a/engines/director/cast.cpp b/engines/director/cast.cpp
index 7d3f905bcf7..ec0eaae4a78 100644
--- a/engines/director/cast.cpp
+++ b/engines/director/cast.cpp
@@ -518,6 +518,22 @@ void Cast::loadCast() {
 		delete r;
 	}
 
+	// Cast library mapping, used in V5 and up
+	if ((r = _castArchive->getMovieResourceIfPresent(MKTAG('M', 'C', 's', 'L'))) != nullptr) {
+		loadCastLibMapping(*r);
+		delete r;
+	}
+
+	Common::Array<uint16> cinf = _castArchive->getResourceIDList(MKTAG('C', 'i', 'n', 'f'));
+	if (cinf.size() > 0) {
+		debugC(2, kDebugLoading, "****** Loading %d CastLibInfos Cinf", cinf.size());
+
+		for (Common::Array<uint16>::iterator iterator = cinf.begin(); iterator != cinf.end(); ++iterator) {
+			loadCastLibInfo(*(r = _castArchive->getResource(MKTAG('C', 'i', 'n', 'f'), *iterator)), *iterator);
+			delete r;
+		}
+	}
+
 	Common::Array<uint16> vwci = _castArchive->getResourceIDList(MKTAG('V', 'W', 'C', 'I'));
 	if (vwci.size() > 0) {
 		debugC(2, kDebugLoading, "****** Loading %d CastInfos VWCI", vwci.size());
@@ -1499,6 +1515,40 @@ void Cast::loadCastInfo(Common::SeekableReadStreamEndian &stream, uint16 id) {
 	_castsInfo[id] = ci;
 }
 
+void Cast::loadCastLibMapping(Common::SeekableReadStreamEndian &stream) {
+	if (debugChannelSet(8, kDebugLoading)) {
+		stream.hexdump(stream.size());
+	}
+	stream.readUint32(); // header size
+	uint32 count = stream.readUint32();
+	stream.readUint16();
+	uint32 unkCount = stream.readUint32() + 1;
+	for (uint32 i = 0; i < unkCount; i++) {
+		stream.readUint32();
+	}
+	for (uint32 i = 0; i < count; i++) {
+		int nameSize = stream.readByte() + 1;
+		Common::String name = stream.readString('\0', nameSize);
+		int pathSize = stream.readByte() + 1;
+		Common::String path = stream.readString('\0', pathSize);
+		if (pathSize > 1)
+			stream.readUint16();
+		stream.readUint16();
+		uint16 itemCount = stream.readUint16();
+		stream.readUint16();
+		uint16 libId = stream.readUint16();
+		debugC(5, kDebugLoading, "Cast::loadCastLibMapping: name: %s, path: %s, itemCount: %d, libId: %d", name.c_str(), path.c_str(), itemCount, libId);
+	}
+	return;
+}
+
+void Cast::loadCastLibInfo(Common::SeekableReadStreamEndian &stream, uint16 id) {
+	if (debugChannelSet(8, kDebugLoading)) {
+		stream.hexdump(stream.size());
+	}
+	debugC(5, kDebugLoading, "Cast::loadCastLibInfo(): %d", id);
+}
+
 Common::CodePage Cast::getFileEncoding() {
 	// Returns the default encoding for the file this cast is contained in.
 	// This depends on which platform the file was made on.
diff --git a/engines/director/cast.h b/engines/director/cast.h
index 55aa3ed9987..b94c86982f1 100644
--- a/engines/director/cast.h
+++ b/engines/director/cast.h
@@ -89,6 +89,8 @@ public:
 	void loadCastDataVWCR(Common::SeekableReadStreamEndian &stream);
 	void loadCastData(Common::SeekableReadStreamEndian &stream, uint16 id, Resource *res);
 	void loadCastInfo(Common::SeekableReadStreamEndian &stream, uint16 id);
+	void loadCastLibMapping(Common::SeekableReadStreamEndian &stream);
+	void loadCastLibInfo(Common::SeekableReadStreamEndian &stream, uint16 id);
 	void loadLingoContext(Common::SeekableReadStreamEndian &stream);
 	void loadExternalSound(Common::SeekableReadStreamEndian &stream);
 	void loadSord(Common::SeekableReadStreamEndian &stream);


Commit: f0343d6f02e11414a0e781068e0cf9e459c8f36b
    https://github.com/scummvm/scummvm/commit/f0343d6f02e11414a0e781068e0cf9e459c8f36b
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: Add stubs for BatQT XObj

Changed paths:
    engines/director/lingo/xlibs/batqt.cpp
    engines/director/lingo/xlibs/batqt.h


diff --git a/engines/director/lingo/xlibs/batqt.cpp b/engines/director/lingo/xlibs/batqt.cpp
index 819851fd1a6..4e28933ffdf 100644
--- a/engines/director/lingo/xlibs/batqt.cpp
+++ b/engines/director/lingo/xlibs/batqt.cpp
@@ -68,7 +68,27 @@ const char *BatQT::fileNames[] = {
 };
 
 static MethodProto xlibMethods[] = {
-	{ "new",		BatQT::m_new,			 0, 0,	400 },	// D4
+	{ "new",			BatQT::m_new,			0, 0,	400 },	// D4
+	{ "dispose",		BatQT::m_dispose,		1, 1,	400 },	// D4
+	{ "name",			BatQT::m_name,			0, 0,	400 },	// D4
+	{ "status",			BatQT::m_status,		0, 0,	400 },	// D4
+	{ "error",			BatQT::m_error,			1, 1,	400 },	// D4
+	{ "lastError",		BatQT::m_lastError,		0, 0,	400 },	// D4
+	{ "open",			BatQT::m_open,			2, 2,	400 },	// D4
+	{ "play",			BatQT::m_play,			3, 3,	400 },	// D4
+	{ "stop",			BatQT::m_stop,			0, 0,	400 },  // D4
+	{ "getTimeRange",	BatQT::m_getTimeRange,	0, 0,	400 },  // D4
+	{ "getMovieBox",	BatQT::m_getMovieBox,	0, 0,	400 },  // D4
+	{ "getTime",		BatQT::m_getTime,		0, 0,	400 },  // D4
+	{ "setTime",		BatQT::m_setTime,		1, 1,	400 },  // D4
+	{ "setVolume",		BatQT::m_setVolume,		1, 1,	400 },  // D4
+	{ "length",			BatQT::m_length,		0, 0,	400 },  // D4
+	{ "setMovieBox",	BatQT::m_setMovieBox,	4, 4,	400 },  // D4
+	{ "setTimeRange",	BatQT::m_setTimeRange,	2, 2,	400 },  // D4
+	{ "addCallback",	BatQT::m_addCallback,	1, 1,	400 },  // D4
+	{ "removeCallback",	BatQT::m_removeCallback,1, 1,	400 },  // D4
+	{ "resetCallbacks",	BatQT::m_resetCallbacks,0, 0,	400 },  // D4
+	{ "setBatch",		BatQT::m_setBatch,		1, 1,	400 },  // D4
 	{ nullptr, nullptr, 0, 0, 0 }
 };
 
@@ -88,7 +108,7 @@ void BatQT::close(int type) {
 }
 
 
-BatQTXObject::BatQTXObject(ObjectType ObjectType) : Object<BatQTXObject>("FindSys") {
+BatQTXObject::BatQTXObject(ObjectType ObjectType) : Object<BatQTXObject>("BatQt") {
 	_objType = ObjectType;
 }
 
@@ -96,4 +116,123 @@ void BatQT::m_new(int nargs) {
 	g_lingo->push(g_lingo->_state->me);
 }
 
+void BatQT::m_dispose(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_dispose", nargs);
+	g_lingo->dropStack(nargs);
+}
+
+void BatQT::m_name(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_name", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum(""));
+}
+
+void BatQT::m_status(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_status", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum(0));
+}
+
+void BatQT::m_error(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_error", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum(""));
+}
+
+void BatQT::m_lastError(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_lastError", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum(""));
+}
+
+void BatQT::m_open(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_open", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum(0));
+}
+
+void BatQT::m_play(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_play", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum(0));
+}
+
+void BatQT::m_stop(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_stop", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum(0));
+}
+
+void BatQT::m_getTimeRange(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_getTimeRange", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum(""));
+}
+
+void BatQT::m_getMovieBox(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_getMovieBox", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum("0,0,320,240"));
+}
+
+void BatQT::m_getTime(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_getTime", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum(0));
+}
+
+void BatQT::m_setTime(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_setTime", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum(""));
+}
+
+void BatQT::m_setVolume(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_setVolume", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum(""));
+}
+
+void BatQT::m_length(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_length", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum(0));
+}
+
+void BatQT::m_setMovieBox(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_setMovieBox", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum(0));
+}
+
+void BatQT::m_setTimeRange(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_setTimeRange", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum(0));
+}
+
+void BatQT::m_addCallback(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_addCallback", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum(0));
+}
+
+void BatQT::m_removeCallback(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_removeCallback", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum(0));
+}
+
+void BatQT::m_resetCallbacks(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_resetCallbacks", nargs);
+	g_lingo->dropStack(nargs);
+	g_lingo->push(Datum(0));
+}
+
+void BatQT::m_setBatch(int nargs) {
+	g_lingo->printSTUBWithArglist("BatQT::m_setBatch", nargs);
+	g_lingo->dropStack(nargs);
+}
+
+
 } // End of namespace Director
diff --git a/engines/director/lingo/xlibs/batqt.h b/engines/director/lingo/xlibs/batqt.h
index fb75f5b4573..f6faa25b7a1 100644
--- a/engines/director/lingo/xlibs/batqt.h
+++ b/engines/director/lingo/xlibs/batqt.h
@@ -38,6 +38,26 @@ void open(int type);
 void close(int type);
 
 void m_new(int nargs);
+void m_dispose(int nargs);
+void m_name(int nargs);
+void m_status(int nargs);
+void m_error(int nargs);
+void m_lastError(int nargs);
+void m_open(int nargs);
+void m_play(int nargs);
+void m_stop(int nargs);
+void m_getTimeRange(int nargs);
+void m_getMovieBox(int nargs);
+void m_getTime(int nargs);
+void m_setTime(int nargs);
+void m_setVolume(int nargs);
+void m_length(int nargs);
+void m_setMovieBox(int nargs);
+void m_setTimeRange(int nargs);
+void m_addCallback(int nargs);
+void m_removeCallback(int nargs);
+void m_resetCallbacks(int nargs);
+void m_setBatch(int nargs);
 
 } // End of namespace BatQT
 


Commit: 9ff4641c68026f16ad16a6725528b0fa05fe8d8c
    https://github.com/scummvm/scummvm/commit/9ff4641c68026f16ad16a6725528b0fa05fe8d8c
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: LINGO: Add RECT support to getObjectProp

Fixes starting a new game in Operation: Eco-Nightmare

Changed paths:
    engines/director/lingo/lingo-the.cpp


diff --git a/engines/director/lingo/lingo-the.cpp b/engines/director/lingo/lingo-the.cpp
index 3c05ed2a730..cd249334012 100644
--- a/engines/director/lingo/lingo-the.cpp
+++ b/engines/director/lingo/lingo-the.cpp
@@ -1889,6 +1889,21 @@ void Lingo::getObjectProp(Datum &obj, Common::String &propName) {
 		g_lingo->push(d);
 		return;
 	}
+	if (obj.type == RECT) {
+		if (propName.equalsIgnoreCase("left")) {
+			d = obj.u.farr->arr[0];
+		} else if (propName.equalsIgnoreCase("top")) {
+			d = obj.u.farr->arr[1];
+		} else if (propName.equalsIgnoreCase("right")) {
+			d = obj.u.farr->arr[2];
+		} else if (propName.equalsIgnoreCase("bottom")) {
+			d = obj.u.farr->arr[3];
+		} else {
+			g_lingo->lingoError("Lingo::getObjectProp: Rect <%s> has no property '%s'", obj.asString(true).c_str(), propName.c_str());
+		}
+		g_lingo->push(d);
+		return;
+	}
 	if (obj.type == CASTREF) {
 		Movie *movie = _vm->getCurrentMovie();
 		if (!movie) {


Commit: f4cbadaca27e42bde2a7905cd9e8e5eea255e233
    https://github.com/scummvm/scummvm/commit/f4cbadaca27e42bde2a7905cd9e8e5eea255e233
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: XOBJ: FindSys must return a path with a slash

Changed paths:
    engines/director/lingo/xlibs/findsys.cpp


diff --git a/engines/director/lingo/xlibs/findsys.cpp b/engines/director/lingo/xlibs/findsys.cpp
index 3021e6e47a7..7fd46316687 100644
--- a/engines/director/lingo/xlibs/findsys.cpp
+++ b/engines/director/lingo/xlibs/findsys.cpp
@@ -84,7 +84,7 @@ void FindSys::m_new(int nargs) {
 }
 
 void FindSys::m_do(int nargs) {
-	g_lingo->push(Common::String("C:\\WINDOWS"));
+	g_lingo->push(Common::String("C:\\WINDOWS\\"));
 }
 
 } // End of namespace Director


Commit: a407f165a6c227db7e11128eae66d5b2b4fedd81
    https://github.com/scummvm/scummvm/commit/a407f165a6c227db7e11128eae66d5b2b4fedd81
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: XOBJ: Remove directory from fileio paths

Jamming an absolute Windows path into a filename is allowed on Linux,
however Windows isn't as forgiving.

It would be nice to preserve the full path of the saved file;
unfortunately the SaveFileManager API doesn't support subdirectories.
For now just keep the old pattern of [gameid]-[filename].txt.

Fixes save games in Operation: Eco-Nightmare

Changed paths:
    engines/director/lingo/xlibs/fileio.cpp


diff --git a/engines/director/lingo/xlibs/fileio.cpp b/engines/director/lingo/xlibs/fileio.cpp
index bcb89c98916..1462469c9f2 100644
--- a/engines/director/lingo/xlibs/fileio.cpp
+++ b/engines/director/lingo/xlibs/fileio.cpp
@@ -232,11 +232,11 @@ void FileIO::m_new(int nargs) {
 	}
 
 	// Enforce target to the created files so they do not mix up
-	Common::String filename = lastPathComponent(path, dirSeparator);
-	Common::String dir = firstPathComponents(path, dirSeparator);
+	Common::String filenameOrig = lastPathComponent(path, dirSeparator);
 
+	Common::String filename = filenameOrig;
 	if (!filename.hasPrefixIgnoreCase(prefix))
-		filename = dir + prefix + filename;
+		filename = prefix + filenameOrig;
 
 	if (option.equalsIgnoreCase("read")) {
 		me->_inStream = saves->openForLoading(filename);


Commit: 4e1b32c0d4f2443f0f4d8d823296d2c587cb01fc
    https://github.com/scummvm/scummvm/commit/4e1b32c0d4f2443f0f4d8d823296d2c587cb01fc
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: Add filesystem quirk for teamxtreme2

Changed paths:
    engines/director/game-quirks.cpp


diff --git a/engines/director/game-quirks.cpp b/engines/director/game-quirks.cpp
index b2d8ec5d6cc..3e75f4da65c 100644
--- a/engines/director/game-quirks.cpp
+++ b/engines/director/game-quirks.cpp
@@ -73,6 +73,13 @@ struct CachedFile {
 		"WOLFGANG.dat",	// It needs an empty file
 			(const byte *)"", 0
 	},
+	{ "teamxtreme2", Common::kPlatformWindows,
+		// In Operation: Eco-Nightmare, the game will try and check if the
+		// save file exists with getNthFileNameInFolder before attempting to
+		// read it with FileIO (which uses the save data store).
+		"WINDOWS/TX2SAVES",
+			(const byte *)"", 0
+	},
 	{ nullptr, Common::kPlatformUnknown, nullptr, nullptr, 0 }
 };
 


Commit: 8ff91ce024c550c177706dd46b6799dc2fb25c30
    https://github.com/scummvm/scummvm/commit/8ff91ce024c550c177706dd46b6799dc2fb25c30
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: LINGO: Make b_getNthFileNameInFolder always check cache

It's possible for games to request files that don't exist in the game
path, e.g. C:\WINDOWS\TX2SAVES. As such, the method needs to always
include the quirk files, regardless of whether the path exists.

Fixes loading save games in Operation: Eco-Nightmare

Changed paths:
    engines/director/lingo/lingo-builtins.cpp


diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp
index 79cdd393542..64dd09278fd 100644
--- a/engines/director/lingo/lingo-builtins.cpp
+++ b/engines/director/lingo/lingo-builtins.cpp
@@ -1154,36 +1154,37 @@ void LB::b_getNthFileNameInFolder(int nargs) {
 	}
 
 	Datum r;
+	Common::Array<Common::String> fileNameList;
+
+	// First, mix in any files injected from the quirks
+	Common::Archive *cache = SearchMan.getArchive(kQuirksCacheArchive);
+	if (cache) {
+		Common::ArchiveMemberList files;
+
+		cache->listMatchingMembers(files, path + (path.empty() ? "*" : "/*"), true);
+
+		for (auto &fi : files) {
+			fileNameList.push_back(Common::lastPathComponent(fi->getName(), '/'));
+		}
+	}
+
+	// Next, mix in files from the game filesystem (if they exist)
 	if (d.exists()) {
 		Common::FSList f;
 		if (!d.getChildren(f, Common::FSNode::kListAll)) {
 			warning("Cannot access directory %s", path.c_str());
 		} else {
-			if ((uint)fileNum < f.size()) {
-				// here, we sort all the fileNames
-				Common::Array<Common::String> fileNameList;
-				for (uint i = 0; i < f.size(); i++)
-					fileNameList.push_back(f[i].getName());
-
-				// Now mix in any files coming from the quirks
-				Common::Archive *cache = SearchMan.getArchive(kQuirksCacheArchive);
-
-				if (cache) {
-					Common::ArchiveMemberList files;
-
-					cache->listMatchingMembers(files, path + (path.empty() ? "*" : "/*"), true);
-
-					for (auto &fi : files) {
-						fileNameList.push_back(fi->getName().c_str());
-					}
-				}
-
-				Common::sort(fileNameList.begin(), fileNameList.end());
-				r = Datum(fileNameList[fileNum]);
-			}
+			for (uint i = 0; i < f.size(); i++)
+				fileNameList.push_back(f[i].getName());
 		}
 	}
 
+	if (!fileNameList.empty() && (uint)fileNum < fileNameList.size()) {
+		// Sort files alphabetically
+		Common::sort(fileNameList.begin(), fileNameList.end());
+		r = Datum(fileNameList[fileNum]);
+	}
+
 	g_lingo->push(r);
 }
 


Commit: ef3d3605527654c7ed258b1ccefa9eaf04cba263
    https://github.com/scummvm/scummvm/commit/ef3d3605527654c7ed258b1ccefa9eaf04cba263
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: Unify arguments to Cast::loadXData functions

Changed paths:
    engines/director/cast.cpp
    engines/director/cast.h


diff --git a/engines/director/cast.cpp b/engines/director/cast.cpp
index ec0eaae4a78..0c919af0d5a 100644
--- a/engines/director/cast.cpp
+++ b/engines/director/cast.cpp
@@ -632,27 +632,27 @@ void Cast::loadStxtData(int key, TextCastMember *member) {
 	}
 }
 
-void Cast::loadPaletteData(PaletteCastMember *member) {
+void Cast::loadPaletteData(int key, PaletteCastMember *member) {
 	// TODO: Verify how palettes work in >D4 versions
 	int paletteId = 0;
 	if (_version >= kFileVer400 && _version < kFileVer500 && member->_children.size() == 1) {
 		paletteId = member->_children[0].index;
 	} else if (_version < kFileVer400) {
 		// For D3 and below, palette IDs are stored in the CLUT resource as cast ID + 1024
-		paletteId = member->getID() + _castIDoffset;
+		paletteId = key + _castIDoffset;
 	} else {
 		warning("Cast::loadPaletteData(): Expected 1 child for palette cast, got %d", member->_children.size());
 	}
 	if (paletteId) {
-		debugC(2, kDebugImages, "Cast::loadPaletteData(): linking palette id %d to cast member %d", paletteId, member->getID());
+		debugC(2, kDebugImages, "Cast::loadPaletteData(): linking palette id %d to cast index %d", paletteId, key);
 		member->_palette = g_director->getPalette(paletteId);
 	}
 }
 
-void Cast::loadFilmLoopData(FilmLoopCastMember *member) {
+void Cast::loadFilmLoopData(int key, FilmLoopCastMember *member) {
 	if (_version < kFileVer400) {
 		// Director 3 and below should have a SCVW resource
-		uint16 filmLoopId = member->getID() + _castIDoffset;
+		uint16 filmLoopId = key + _castIDoffset;
 		uint32 tag = MKTAG('S', 'C', 'V', 'W');
 		Common::SeekableReadStreamEndian *loop = _castArchive->getResource(tag, filmLoopId);
 		debugC(2, kDebugLoading, "****** Loading '%s' id: %d, %d bytes", tag2str(tag), filmLoopId, (int)loop->size());
@@ -868,10 +868,10 @@ void Cast::loadCastMemberData() {
 
 		switch (c->_value->_type){
 			case kCastPalette:
-				loadPaletteData((PaletteCastMember *)c->_value);
+				loadPaletteData(c->_key, (PaletteCastMember *)c->_value);
 				break;
 			case kCastFilmLoop:
-				loadFilmLoopData((FilmLoopCastMember *)c->_value);
+				loadFilmLoopData(c->_key, (FilmLoopCastMember *)c->_value);
 				break;
 			case kCastBitmap:
 				loadBitmapData(c->_key, (BitmapCastMember *)c->_value);
diff --git a/engines/director/cast.h b/engines/director/cast.h
index b94c86982f1..7de16e2b8fb 100644
--- a/engines/director/cast.h
+++ b/engines/director/cast.h
@@ -97,8 +97,8 @@ public:
 
 	void loadCastMemberData();
 	void loadStxtData(int key, TextCastMember *member);
-	void loadPaletteData(PaletteCastMember *member);
-	void loadFilmLoopData(FilmLoopCastMember *member);
+	void loadPaletteData(int key, PaletteCastMember *member);
+	void loadFilmLoopData(int key, FilmLoopCastMember *member);
 	void loadBitmapData(int key, BitmapCastMember *bitmapCast);
 	void loadSoundData(int key, SoundCastMember *soundCast);
 


Commit: f703ee6ecf52b69f56da42f0f7b13d536d78bde8
    https://github.com/scummvm/scummvm/commit/f703ee6ecf52b69f56da42f0f7b13d536d78bde8
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: Split CastMember classes into files

Changed paths:
  A engines/director/castmember/bitmap.cpp
  A engines/director/castmember/bitmap.h
  A engines/director/castmember/castmember.cpp
  A engines/director/castmember/castmember.h
  A engines/director/castmember/digitalvideo.cpp
  A engines/director/castmember/digitalvideo.h
  A engines/director/castmember/filmloop.cpp
  A engines/director/castmember/filmloop.h
  A engines/director/castmember/movie.cpp
  A engines/director/castmember/movie.h
  A engines/director/castmember/palette.cpp
  A engines/director/castmember/palette.h
  A engines/director/castmember/script.cpp
  A engines/director/castmember/script.h
  A engines/director/castmember/shape.cpp
  A engines/director/castmember/shape.h
  A engines/director/castmember/sound.cpp
  A engines/director/castmember/sound.h
  A engines/director/castmember/text.cpp
  A engines/director/castmember/text.h
  R engines/director/castmember.cpp
  R engines/director/castmember.h
    engines/director/cast.cpp
    engines/director/channel.cpp
    engines/director/cursor.cpp
    engines/director/debugger.cpp
    engines/director/events.cpp
    engines/director/graphics.cpp
    engines/director/lingo/lingo-builtins.cpp
    engines/director/lingo/lingo-bytecode.cpp
    engines/director/lingo/lingo-code.cpp
    engines/director/lingo/lingo-funcs.cpp
    engines/director/lingo/lingo-object.cpp
    engines/director/lingo/lingo-the.cpp
    engines/director/lingo/lingo.cpp
    engines/director/module.mk
    engines/director/resource.cpp
    engines/director/score.cpp
    engines/director/score.h
    engines/director/sound.cpp
    engines/director/sprite.cpp
    engines/director/window.cpp


diff --git a/engines/director/cast.cpp b/engines/director/cast.cpp
index 0c919af0d5a..301b26061fc 100644
--- a/engines/director/cast.cpp
+++ b/engines/director/cast.cpp
@@ -33,13 +33,22 @@
 
 #include "director/director.h"
 #include "director/cast.h"
-#include "director/castmember.h"
 #include "director/images.h"
 #include "director/movie.h"
 #include "director/score.h"
 #include "director/sound.h"
 #include "director/stxt.h"
 #include "director/util.h"
+#include "director/castmember/castmember.h"
+#include "director/castmember/bitmap.h"
+#include "director/castmember/digitalvideo.h"
+#include "director/castmember/filmloop.h"
+#include "director/castmember/movie.h"
+#include "director/castmember/palette.h"
+#include "director/castmember/script.h"
+#include "director/castmember/shape.h"
+#include "director/castmember/sound.h"
+#include "director/castmember/text.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
 
diff --git a/engines/director/castmember.cpp b/engines/director/castmember.cpp
deleted file mode 100644
index cc0fd428dbf..00000000000
--- a/engines/director/castmember.cpp
+++ /dev/null
@@ -1,1791 +0,0 @@
-/* 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 <http://www.gnu.org/licenses/>.
- *
- */
-
-#include "graphics/macgui/macbutton.h"
-#include "graphics/surface.h"
-#include "image/image_decoder.h"
-#include "video/avi_decoder.h"
-#include "video/qt_decoder.h"
-
-#include "director/director.h"
-#include "director/cast.h"
-#include "director/castmember.h"
-#include "director/cursor.h"
-#include "director/channel.h"
-#include "director/movie.h"
-#include "director/picture.h"
-#include "director/score.h"
-#include "director/sprite.h"
-#include "director/sound.h"
-#include "director/window.h"
-#include "director/stxt.h"
-#include "director/sprite.h"
-
-namespace Director {
-
-CastMember::CastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream) : Object<CastMember>("CastMember") {
-	_type = kCastTypeNull;
-	_cast = cast;
-	_castId = castId;
-	_hilite = false;
-	_purgePriority = 3;
-	_size = stream.size();
-	_flags1 = 0;
-
-	_modified = true;
-	_isChanged = false;
-
-	_objType = kCastMemberObj;
-
-	_widget = nullptr;
-	_erase = false;
-}
-
-CastMember::CastMember(Cast *cast, uint16 castId) : Object<CastMember>("CastMember") {
-	_type = kCastTypeNull;
-	_cast = cast;
-	_castId = castId;
-	_hilite = false;
-	_purgePriority = 3;
-	_size = 0;
-	_flags1 = 0;
-
-	_modified = true;
-	_isChanged = false;
-
-	_objType = kCastMemberObj;
-
-	_widget = nullptr;
-	_erase = false;
-}
-
-CastMemberInfo *CastMember::getInfo() {
-	return _cast->getCastMemberInfo(_castId);
-}
-
-void CastMember::setModified(bool modified) {
-	_modified = modified;
-	if (modified)
-		_isChanged = true;
-}
-
-
-/////////////////////////////////////
-// Bitmap
-/////////////////////////////////////
-
-BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint32 castTag, uint16 version, uint8 flags1)
-		: CastMember(cast, castId, stream) {
-	_type = kCastBitmap;
-	_picture = nullptr;
-	_ditheredImg = nullptr;
-	_matte = nullptr;
-	_noMatte = false;
-	_bytes = 0;
-	_pitch = 0;
-	_flags2 = 0;
-	_regX = _regY = 0;
-	_clut = 0;
-	_ditheredTargetClut = 0;
-	_bitsPerPixel = 0;
-	_external = false;
-
-	if (version < kFileVer400) {
-		_flags1 = flags1;	// region: 0 - auto, 1 - matte, 2 - disabled
-
-		_bytes = stream.readUint16();
-		_initialRect = Movie::readRect(stream);
-		_boundingRect = Movie::readRect(stream);
-		_regY = stream.readUint16();
-		_regX = stream.readUint16();
-
-		if (_bytes & 0x8000) {
-			_bitsPerPixel = stream.readUint16();
-			_clut = stream.readSint16();
-			if (_clut <= 0) // builtin palette
-				_clut -= 1;
-		} else {
-			_bitsPerPixel = 1;
-			_clut = kClutSystemMac;
-		}
-
-		_pitch = _initialRect.width();
-		if (_pitch % 16)
-			_pitch += 16 - (_initialRect.width() % 16);
-
-		_pitch *= _bitsPerPixel;
-		_pitch >>= 3;
-
-	} else if (version >= kFileVer400 && version < kFileVer600) {
-		_flags1 = flags1;
-		_pitch = stream.readUint16();
-		_pitch &= 0x0fff;
-
-		_initialRect = Movie::readRect(stream);
-		_boundingRect = Movie::readRect(stream);
-		_regY = stream.readUint16();
-		_regX = stream.readUint16();
-
-		_bitsPerPixel = stream.readUint16();
-
-		if (stream.eos()) {
-			_bitsPerPixel = 0;
-		} else {
-			if (version >= kFileVer500) {
-				stream.readSint16(); // is this the castlib? was ff ff
-			}
-			_clut = stream.readSint16();
-			if (_clut <= 0) // builtin palette
-				_clut -= 1;
-			stream.readUint16();
-			/* uint16 unk1 = */ stream.readUint16();
-			stream.readUint16();
-
-			stream.readUint32();
-			stream.readUint32();
-
-			_flags2 = stream.readUint16();
-		}
-
-		if (_bitsPerPixel == 0)
-			_bitsPerPixel = 1;
-
-		int tail = 0;
-		byte buf[256];
-
-		while (!stream.eos()) {
-			byte c = stream.readByte();
-			if (tail < 256)
-				buf[tail] = c;
-			tail++;
-		}
-
-		if (tail)
-			warning("BUILDBOT: BitmapCastMember: %d bytes left", tail);
-
-		if (tail && debugChannelSet(2, kDebugLoading)) {
-			debug("BitmapCastMember: tail");
-			Common::hexdump(buf, tail);
-		}
-	}
-
-	_tag = castTag;
-}
-
-BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, Image::ImageDecoder *img, uint8 flags1)
-	: CastMember(cast, castId) {
-	_type = kCastBitmap;
-	_matte = nullptr;
-	_noMatte = false;
-	_bytes = 0;
-	if (img != nullptr) {
-		_picture = new Picture(*img);
-	}
-	_ditheredImg = nullptr;
-	_clut = -1;
-	_ditheredTargetClut = 0;
-	_initialRect = Common::Rect(0, 0, img->getSurface()->w, img->getSurface()->h);
-	_pitch = img->getSurface()->pitch;
-	_bitsPerPixel = img->getSurface()->format.bytesPerPixel * 8;
-	_regY = img->getSurface()->h / 2;
-	_regX = img->getSurface()->w / 2;
-	_flags1 = flags1;
-	_flags2 = 0;
-	_tag = 0;
-	_external = false;
-}
-
-BitmapCastMember::~BitmapCastMember() {
-	delete _picture;
-
-	if (_ditheredImg) {
-		_ditheredImg->free();
-		delete _ditheredImg;
-	}
-
-	if (_matte)
-		delete _matte;
-}
-
-Graphics::MacWidget *BitmapCastMember::createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) {
-	if (!_picture) {
-		warning("BitmapCastMember::createWidget: No picture");
-		return nullptr;
-	}
-
-	// skip creating widget when the bbox is not available, maybe we should create it using initialRect
-	if (!bbox.width() || !bbox.height())
-		return nullptr;
-
-	// Check if we need to dither the image
-	int dstBpp = g_director->_wm->_pixelformat.bytesPerPixel;
-	int srcBpp = _picture->_surface.format.bytesPerPixel;
-
-	const byte *pal = _picture->_palette;
-	bool previouslyDithered = _ditheredImg != nullptr;
-	if (_ditheredImg) {
-		_ditheredImg->free();
-		delete _ditheredImg;
-		_ditheredImg = nullptr;
-		_ditheredTargetClut = 0;
-	}
-
-	if (dstBpp == 1) {
-		if (srcBpp > 1
-		// At least early directors were not remapping 8bpp images. But in case it is
-		// needed, here is the code
-#if 0
-		|| (srcBpp == 1 &&
-			memcmp(g_director->_wm->getPalette(), _img->_palette, _img->_paletteSize))
-#endif
-			) {
-
-			_ditheredImg = _picture->_surface.convertTo(g_director->_wm->_pixelformat, _picture->_palette, _picture->_paletteColors, g_director->_wm->getPalette(), g_director->_wm->getPaletteSize());
-
-			pal = g_director->_wm->getPalette();
-		} else {
-			// Convert indexed image to indexed palette
-			Movie *movie = g_director->getCurrentMovie();
-			Cast *cast = movie->getCast();
-			Score *score = movie->getScore();
-			// Get the current score palette. Note that this is the ID of the palette in the list, not the cast member!
-			int currentPaletteId = score->resolvePaletteId(score->getCurrentPalette());
-			if (!currentPaletteId)
-				currentPaletteId = cast->_defaultPalette;
-			PaletteV4 *currentPalette = g_director->getPalette(currentPaletteId);
-			if (!currentPalette) {
-				currentPaletteId = kClutSystemMac;
-				currentPalette = g_director->getPalette(currentPaletteId);
-			}
-			int castPaletteId = score->resolvePaletteId(_clut);
-			// It is possible for Director to have saved an invalid ID in _clut;
-			// if this is the case, do no dithering.
-			if (!castPaletteId)
-				castPaletteId = currentPaletteId;
-
-			// Check if the palette is in the middle of a color fade event
-			bool isColorCycling = score->isPaletteColorCycling();
-
-			// First, check if the palettes are different
-			switch (_bitsPerPixel) {
-			// 1bpp - this is preconverted to 0x00 and 0xff, change nothing.
-			case 1:
-				break;
-			// 2bpp - convert to nearest using the standard 2-bit palette.
-			case 2:
-				{
-					const PaletteV4 &srcPal = g_director->getLoaded4Palette();
-					_ditheredImg = _picture->_surface.convertTo(g_director->_wm->_pixelformat, srcPal.palette, srcPal.length, currentPalette->palette, currentPalette->length, Graphics::kDitherNaive);
-				}
-				break;
-			// 4bpp - if using a builtin palette, use one of the corresponding 4-bit ones.
-			case 4:
-				{
-					const auto pals = g_director->getLoaded16Palettes();
-					// in D4 you aren't allowed to use custom palettes for 4-bit images, so uh...
-					// I guess default to the mac palette?
-					int palIndex = pals.contains(castPaletteId) ? castPaletteId : kClutSystemMac;
-					const PaletteV4 &srcPal = pals.getVal(palIndex);
-					_ditheredImg = _picture->_surface.convertTo(g_director->_wm->_pixelformat, srcPal.palette, srcPal.length, currentPalette->palette, currentPalette->length, Graphics::kDitherNaive);
-				}
-				break;
-			// 8bpp - if using a different palette, and we're not doing a color cycling operation, convert using nearest colour matching
-			case 8:
-				// Only redither 8-bit images if we have the flag set, or it is external
-				if (!movie->_remapPalettesWhenNeeded && !_external)
-					break;
-				if (_external || (castPaletteId != currentPaletteId && !isColorCycling)) {
-					const auto pals = g_director->getLoadedPalettes();
-					int palIndex = pals.contains(castPaletteId) ? castPaletteId : kClutSystemMac;
-					const PaletteV4 &srcPal = pals.getVal(palIndex);
-
-					// If it is an external image, use the included palette.
-					// For BMP images especially, they'll often have the right colors
-					// but in the wrong palette order.
-					const byte *palPtr = _external ? pal : srcPal.palette;
-					int palLength = _external ? _picture->getPaletteSize() : srcPal.length;
-					_ditheredImg = _picture->_surface.convertTo(g_director->_wm->_pixelformat, palPtr, palLength, currentPalette->palette, currentPalette->length, Graphics::kDitherNaive);
-				}
-				break;
-			default:
-				break;
-			}
-
-			if (_ditheredImg) {
-				debugC(4, kDebugImages, "BitmapCastMember::createWidget(): Dithering image from source palette %d to target palette %d", _clut, score->getCurrentPalette());
-				// Save the palette ID so we can check if a redraw is required
-				_ditheredTargetClut = currentPaletteId;
-
-				if (!_external) {
-					// Finally, the first and last colours in the palette are special. No matter what the palette remap
-					// does, we need to scrub those to be the same.
-					const Graphics::Surface *src = &_picture->_surface;
-					for (int y = 0; y < src->h; y++) {
-						for (int x = 0; x < src->w; x++) {
-							const int test = *(const byte *)src->getBasePtr(x, y);
-							if (test == 0 || test == (1 << _bitsPerPixel) - 1) {
-								*(byte *)_ditheredImg->getBasePtr(x, y) = test == 0 ? 0x00 : 0xff;
-							}
-						}
-					}
-				}
-			} else if (previouslyDithered) {
-				debugC(4, kDebugImages, "BitmapCastMember::createWidget(): Removed dithered image, score palette %d matches cast member", score->getCurrentPalette());
-			}
-
-		}
-	}
-
-	Graphics::MacWidget *widget = new Graphics::MacWidget(g_director->getCurrentWindow(), bbox.left, bbox.top, bbox.width(), bbox.height(), g_director->_wm, false);
-
-	// scale for drawing a different size sprite
-	copyStretchImg(widget->getSurface()->surfacePtr(), bbox, pal);
-
-	return widget;
-}
-
-void BitmapCastMember::copyStretchImg(Graphics::Surface *surface, const Common::Rect &bbox, const byte *pal) {
-	const Graphics::Surface *srcSurf;
-
-	if (_ditheredImg)
-		srcSurf = _ditheredImg;
-	else
-		srcSurf = &_picture->_surface;
-
-	if (bbox.width() != _initialRect.width() || bbox.height() != _initialRect.height()) {
-
-		int scaleX = SCALE_THRESHOLD * _initialRect.width() / bbox.width();
-		int scaleY = SCALE_THRESHOLD * _initialRect.height() / bbox.height();
-
-		for (int y = 0, scaleYCtr = 0; y < bbox.height(); y++, scaleYCtr += scaleY) {
-			if (g_director->_wm->_pixelformat.bytesPerPixel == 1) {
-				for (int x = 0, scaleXCtr = 0; x < bbox.width(); x++, scaleXCtr += scaleX) {
-					const byte *src = (const byte *)srcSurf->getBasePtr(scaleXCtr / SCALE_THRESHOLD, scaleYCtr / SCALE_THRESHOLD);
-					*(byte *)surface->getBasePtr(x, y) = *src;
-				}
-			} else {
-				for (int x = 0, scaleXCtr = 0; x < bbox.width(); x++, scaleXCtr += scaleX) {
-					const void *ptr = srcSurf->getBasePtr(scaleXCtr / SCALE_THRESHOLD, scaleYCtr / SCALE_THRESHOLD);
-					int32 color;
-
-					switch (srcSurf->format.bytesPerPixel) {
-					case 1:
-						{
-							color = *(const byte *)ptr * 3;
-							color = surface->format.RGBToColor(pal[color], pal[color + 1], pal[color + 2]);
-						}
-						break;
-					case 4:
-						color = *(const int32 *)ptr;
-						break;
-					default:
-						error("Unimplemented src bpp: %d", srcSurf->format.bytesPerPixel);
-					}
-
-					*(int32 *)surface->getBasePtr(x, y) = color;
-				}
-			}
-		}
-	} else {
-		surface->copyFrom(*srcSurf);
-	}
-
-	if (g_director->_debugDraw & kDebugDrawCast) {
-		surface->frameRect(Common::Rect(0, 0, surface->w, surface->h), g_director->_wm->_colorWhite);
-
-		const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont);
-		font->drawString(surface, Common::String::format("%d", _castId), 2, 2, 10, g_director->_wm->_colorWhite);
-	}
-}
-
-bool BitmapCastMember::isModified() {
-	if (CastMember::isModified()) {
-		// Let's us use "setChanged" when changing the picture through Lingo
-		return true;
-	}
-	// Check for palette changes.
-	// If a bitmap has a custom palette assigned to it, createWidget()
-	// will dither the image so that it fits within the current palette.
-	// When the score palette changes, we need to flag that the widget needs
-	// to be recreated.
-	if (_clut) {
-		Movie *movie = g_director->getCurrentMovie();
-		Cast *cast = movie->getCast();
-		Score *score = movie->getScore();
-		int currentPaletteId = score->resolvePaletteId(score->getCurrentPalette());
-		if (!currentPaletteId)
-			currentPaletteId = cast->_defaultPalette;
-		PaletteV4 *currentPalette = g_director->getPalette(currentPaletteId);
-		if (!currentPalette) {
-			currentPaletteId = kClutSystemMac;
-			currentPalette = g_director->getPalette(currentPaletteId);
-		}
-		int castPaletteId = score->resolvePaletteId(_clut);
-		if (!castPaletteId)
-			castPaletteId = cast->_defaultPalette;
-
-		if (currentPaletteId == castPaletteId) {
-			return _ditheredTargetClut != 0;
-		} else {
-			return _ditheredTargetClut != currentPaletteId;
-		}
-	}
-	return false;
-}
-
-void BitmapCastMember::createMatte(Common::Rect &bbox) {
-	// Like background trans, but all white pixels NOT ENCLOSED by coloured pixels
-	// are transparent
-	Graphics::Surface tmp;
-	tmp.create(bbox.width(), bbox.height(), g_director->_pixelformat);
-
-	copyStretchImg(&tmp, bbox);
-
-	_noMatte = true;
-
-	// Searching white color in the corners
-	uint32 whiteColor = 0;
-	bool colorFound = false;
-
-	if (g_director->_pixelformat.bytesPerPixel == 1) {
-		for (int y = 0; y < tmp.h; y++) {
-			for (int x = 0; x < tmp.w; x++) {
-				byte color = *(byte *)tmp.getBasePtr(x, y);
-
-				if (g_director->getPalette()[color * 3 + 0] == 0xff &&
-						g_director->getPalette()[color * 3 + 1] == 0xff &&
-						g_director->getPalette()[color * 3 + 2] == 0xff) {
-					whiteColor = color;
-					colorFound = true;
-					break;
-				}
-			}
-		}
-	} else {
-		whiteColor = g_director->_wm->_colorWhite;
-		colorFound = true;
-	}
-
-	if (!colorFound) {
-		debugC(1, kDebugImages, "BitmapCastMember::createMatte(): No white color for matte image");
-	} else {
-		delete _matte;
-
-		_matte = new Graphics::FloodFill(&tmp, whiteColor, 0, true);
-
-		for (int yy = 0; yy < tmp.h; yy++) {
-			_matte->addSeed(0, yy);
-			_matte->addSeed(tmp.w - 1, yy);
-		}
-
-		for (int xx = 0; xx < tmp.w; xx++) {
-			_matte->addSeed(xx, 0);
-			_matte->addSeed(xx, tmp.h - 1);
-		}
-
-		_matte->fillMask();
-		_noMatte = false;
-	}
-
-	tmp.free();
-}
-
-Graphics::Surface *BitmapCastMember::getMatte(Common::Rect &bbox) {
-	// Lazy loading of mattes
-	if (!_matte && !_noMatte) {
-		createMatte(bbox);
-	}
-
-	// check for the scale matte
-	Graphics::Surface *surface = _matte ? _matte->getMask() : nullptr;
-	if (surface && (surface->w != bbox.width() || surface->h != bbox.height())) {
-		createMatte(bbox);
-	}
-
-	return _matte ? _matte->getMask() : nullptr;
-}
-
-Common::String BitmapCastMember::formatInfo() {
-	return Common::String::format(
-		"initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, foreColor: %d, backColor: %d, regX: %d, regY: %d, pitch: %d, bitsPerPixel: %d, palette: %d",
-		_initialRect.width(), _initialRect.height(),
-		_initialRect.left, _initialRect.top,
-		_boundingRect.width(), _boundingRect.height(),
-		_boundingRect.left, _boundingRect.top,
-		getForeColor(), getBackColor(),
-		_regX, _regY, _pitch, _bitsPerPixel, _clut
-	);
-}
-
-PictureReference *BitmapCastMember::getPicture() const {
-	auto picture = new PictureReference;
-
-	// Not sure if we can make the assumption that the owning
-	// BitmapCastMember will live as long as any reference,
-	// so we'll make a copy of the Picture.
-	picture->_picture = new Picture(*_picture);
-
-	return picture;
-}
-
-void BitmapCastMember::setPicture(PictureReference &picture) {
-	delete _picture;
-	_picture = new Picture(*picture._picture);
-
-	// Force redither
-	delete _ditheredImg;
-	_ditheredImg = nullptr;
-
-	// Make sure we get redrawn
-	setModified(true);
-	// TODO: Should size be adjusted?
-}
-
-void BitmapCastMember::setPicture(Image::ImageDecoder &image, bool adjustSize) {
-	delete _picture;
-	_picture = new Picture(image);
-	if (adjustSize) {
-		auto surf = image.getSurface();
-		_size = surf->pitch * surf->h + _picture->getPaletteSize();
-	}
-	// Make sure we get redrawn
-	setModified(true);
-}
-
-/////////////////////////////////////
-// DigitalVideo
-/////////////////////////////////////
-
-DigitalVideoCastMember::DigitalVideoCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
-		: CastMember(cast, castId, stream) {
-	_type = kCastDigitalVideo;
-	_video = nullptr;
-	_lastFrame = nullptr;
-	_channel = nullptr;
-
-	_getFirstFrame = false;
-	_duration = 0;
-
-	_initialRect = Movie::readRect(stream);
-	_vflags = stream.readUint32();
-	_frameRate = (_vflags >> 24) & 0xff;
-
-	_frameRateType = kFrameRateDefault;
-	if (_vflags & 0x0800) {
-		_frameRateType = (FrameRateType)((_vflags & 0x3000) >> 12);
-	}
-	_qtmovie = _vflags & 0x8000;
-	_avimovie = _vflags & 0x4000;
-	_preload = _vflags & 0x0400;
-	_enableVideo = !(_vflags & 0x0200);
-	_pausedAtStart = _vflags & 0x0100;
-	_showControls = _vflags & 0x40;
-	_directToStage = _vflags & 0x20;
-	_looping = _vflags & 0x10;
-	_enableSound = _vflags & 0x08;
-	_crop = !(_vflags & 0x02);
-	_center = _vflags & 0x01;
-
-	if (debugChannelSet(2, kDebugLoading))
-		_initialRect.debugPrint(2, "DigitalVideoCastMember(): rect:");
-
-	debugC(2, kDebugLoading, "DigitalVideoCastMember(): flags: (%d 0x%04x)", _vflags, _vflags);
-
-	debugC(2, kDebugLoading, "_frameRate: %d", _frameRate);
-	debugC(2, kDebugLoading, "_frameRateType: %d, _preload: %d, _enableVideo %d, _pausedAtStart %d",
-			_frameRateType, _preload, _enableVideo, _pausedAtStart);
-	debugC(2, kDebugLoading, "_showControls: %d, _looping: %d, _enableSound: %d, _crop %d, _center: %d, _directToStage: %d",
-			_showControls, _looping, _enableSound, _crop, _center, _directToStage);
-	debugC(2, kDebugLoading, "_avimovie: %d, _qtmovie: %d", _avimovie, _qtmovie);
-}
-
-DigitalVideoCastMember::~DigitalVideoCastMember() {
-	if (_lastFrame) {
-		_lastFrame->free();
-		delete _lastFrame;
-	}
-
-	if (_video)
-		delete _video;
-}
-
-bool DigitalVideoCastMember::loadVideo(Common::String path) {
-	// TODO: detect file type (AVI, QuickTime, FLIC) based on magic number,
-	// insert the right video decoder
-
-	if (_video)
-		delete _video;
-
-	_filename = path;
-	_video = new Video::QuickTimeDecoder();
-
-	Common::String path1 = pathMakeRelative(path);
-
-	debugC(2, kDebugLoading | kDebugImages, "Loading video %s -> %s", path.c_str(), path1.c_str());
-	bool result = _video->loadFile(Common::Path(path1, g_director->_dirSeparator));
-	if (!result) {
-		delete _video;
-		_video = new Video::AVIDecoder();
-		result = _video->loadFile(Common::Path(path1, g_director->_dirSeparator));
-		if (!result) {
-		    warning("DigitalVideoCastMember::loadVideo(): format not supported, skipping");
-		    delete _video;
-		    _video = nullptr;
-		}
-	}
-
-	if (result && g_director->_pixelformat.bytesPerPixel == 1) {
-		// Director supports playing back RGB and paletted video in 256 colour mode.
-		// In both cases they are dithered to match the Director palette.
-		byte palette[256 * 3];
-		g_system->getPaletteManager()->grabPalette(palette, 0, 256);
-		_video->setDitheringPalette(palette);
-	}
-
-	return result;
-}
-
-bool DigitalVideoCastMember::isModified() {
-	if (!_video || !_video->isVideoLoaded())
-		return true;
-
-	if (_getFirstFrame)
-		return true;
-
-	if (_channel->_movieRate == 0.0)
-		return false;
-
-	return _video->needsUpdate();
-}
-
-void DigitalVideoCastMember::startVideo(Channel *channel) {
-	_channel = channel;
-
-	if (!_video || !_video->isVideoLoaded()) {
-		warning("DigitalVideoCastMember::startVideo: No video %s", !_video ? "decoder" : "loaded");
-		return;
-	}
-
-	if (_pausedAtStart) {
-		_getFirstFrame = true;
-	} else {
-		if (_channel->_movieRate == 0.0)
-			_channel->_movieRate = 1.0;
-	}
-
-	if (_video->isPlaying())
-		_video->rewind();
-	else
-		_video->start();
-
-	debugC(2, kDebugImages, "STARTING VIDEO %s", _filename.c_str());
-
-	if (_channel->_stopTime == 0)
-		_channel->_stopTime = getMovieTotalTime();
-
-	_duration = getMovieTotalTime();
-}
-
-void DigitalVideoCastMember::stopVideo() {
-	if (!_video || !_video->isVideoLoaded()) {
-		warning("DigitalVideoCastMember::stopVideo: No video decoder");
-		return;
-	}
-
-	_video->stop();
-
-	debugC(2, kDebugImages, "STOPPING VIDEO %s", _filename.c_str());
-}
-
-void DigitalVideoCastMember::rewindVideo() {
-	if (!_video || !_video->isVideoLoaded()) {
-		warning("DigitalVideoCastMember::rewindVideo: No video decoder");
-		return;
-	}
-
-	_video->rewind();
-
-	debugC(2, kDebugImages, "REWINDING VIDEO %s", _filename.c_str());
-}
-
-Graphics::MacWidget *DigitalVideoCastMember::createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) {
-	Graphics::MacWidget *widget = new Graphics::MacWidget(g_director->getCurrentWindow(), bbox.left, bbox.top, bbox.width(), bbox.height(), g_director->_wm, false);
-
-	_channel = channel;
-
-	if (!_video || !_video->isVideoLoaded()) {
-		warning("DigitalVideoCastMember::createWidget: No video decoder");
-		delete widget;
-
-		return nullptr;
-	}
-
-	// Do not render stopped videos
-	if (_channel->_movieRate == 0.0 && !_getFirstFrame && _lastFrame) {
-		widget->getSurface()->blitFrom(*_lastFrame);
-
-		return widget;
-	}
-
-	const Graphics::Surface *frame = _video->decodeNextFrame();
-
-	debugC(1, kDebugImages, "Video time: %d  rate: %f", _channel->_movieTime, _channel->_movieRate);
-
-	if (frame) {
-		if (_lastFrame) {
-			_lastFrame->free();
-			delete _lastFrame;
-		}
-
-		_lastFrame = frame->convertTo(g_director->_pixelformat, g_director->getPalette());
-	}
-	if (_lastFrame)
-		widget->getSurface()->blitFrom(*_lastFrame);
-
-	if (_getFirstFrame) {
-		_video->stop();
-		_getFirstFrame = false;
-	}
-
-	if (_video->endOfVideo()) {
-		if (_looping) {
-			_video->rewind();
-		} else {
-			_channel->_movieRate = 0.0;
-		}
-	}
-
-	return widget;
-}
-
-uint DigitalVideoCastMember::getDuration() {
-	if (!_video || !_video->isVideoLoaded()) {
-		Common::String path = getCast()->getVideoPath(_castId);
-		if (!path.empty())
-			loadVideo(pathMakeRelative(path));
-
-		_duration = getMovieTotalTime();
-	}
-	return _duration;
-}
-
-uint DigitalVideoCastMember::getMovieCurrentTime() {
-	if (!_video)
-		return 0;
-
-	int stamp = MIN<int>(_video->getTime() * 60 / 1000, getMovieTotalTime());
-
-	return stamp;
-}
-
-uint DigitalVideoCastMember::getMovieTotalTime() {
-	if (!_video)
-		return 0;
-
-	int stamp = _video->getDuration().msecs() * 60 / 1000;
-
-	return stamp;
-}
-
-void DigitalVideoCastMember::seekMovie(int stamp) {
-	if (!_video)
-		return;
-
-	_channel->_startTime = stamp;
-
-	Audio::Timestamp dur = _video->getDuration();
-
-	_video->seek(Audio::Timestamp(_channel->_startTime * 1000 / 60, dur.framerate()));
-}
-
-void DigitalVideoCastMember::setStopTime(int stamp) {
-	if (!_video)
-		return;
-
-	_channel->_stopTime = stamp;
-
-	Audio::Timestamp dur = _video->getDuration();
-
-	_video->setEndTime(Audio::Timestamp(_channel->_stopTime * 1000 / 60, dur.framerate()));
-}
-
-void DigitalVideoCastMember::setMovieRate(double rate) {
-	if (!_video)
-		return;
-
-	_channel->_movieRate = rate;
-
-	if (rate < 0.0)
-		warning("STUB: DigitalVideoCastMember::setMovieRate(%g)", rate);
-	else
-		_video->setRate(Common::Rational((int)(rate * 100.0), 100));
-
-	if (_video->endOfVideo())
-		_video->rewind();
-}
-
-void DigitalVideoCastMember::setFrameRate(int rate) {
-	if (!_video)
-		return;
-
-	warning("STUB: DigitalVideoCastMember::setFrameRate(%d)", rate);
-}
-
-Common::String DigitalVideoCastMember::formatInfo() {
-	return Common::String::format(
-		"initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, filename: \"%s\", duration: %d, enableVideo: %d, enableSound: %d, looping: %d, crop: %d, center: %d, showControls: %d",
-		_initialRect.width(), _initialRect.height(),
-		_initialRect.left, _initialRect.top,
-		_boundingRect.width(), _boundingRect.height(),
-		_boundingRect.left, _boundingRect.top,
-		_filename.c_str(), _duration,
-		_enableVideo, _enableSound,
-		_looping, _crop, _center, _showControls
-	);
-}
-
-
-/////////////////////////////////////
-// MovieCasts
-/////////////////////////////////////
-
-MovieCastMember::MovieCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
-		: CastMember(cast, castId, stream) {
-	_type = kCastMovie;
-
-	_initialRect = Movie::readRect(stream);
-	_flags = stream.readUint32();
-
-	_looping = !(_flags & 0x20);
-	_enableScripts = _flags & 0x10;
-	_enableSound = _flags & 0x08;
-	_crop = !(_flags & 0x02);
-	_center = _flags & 0x01;
-
-	if (debugChannelSet(2, kDebugLoading))
-		_initialRect.debugPrint(2, "MovieCastMember(): rect:");
-	debugC(2, kDebugLoading, "MovieCastMember(): flags: (%d 0x%04x)", _flags, _flags);
-	debugC(2, kDebugLoading, "_looping: %d, _enableScripts %d, _enableSound: %d, _crop %d, _center: %d",
-			_looping, _enableScripts, _enableSound, _crop, _center);
-
-}
-
-Common::String MovieCastMember::formatInfo() {
-	return Common::String::format(
-		"initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, enableScripts: %d, enableSound: %d, looping: %d, crop: %d, center: %d",
-		_initialRect.width(), _initialRect.height(),
-		_initialRect.left, _initialRect.top,
-		_boundingRect.width(), _boundingRect.height(),
-		_boundingRect.left, _boundingRect.top,
-		_enableScripts, _enableSound, _looping,
-		_crop, _center
-	);
-}
-
-/////////////////////////////////////
-// Film loops
-/////////////////////////////////////
-
-FilmLoopCastMember::FilmLoopCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
-		: CastMember(cast, castId, stream) {
-	_type = kCastFilmLoop;
-	_looping = true;
-	_enableSound = true;
-	_crop = false;
-	_center = false;
-}
-
-FilmLoopCastMember::~FilmLoopCastMember() {
-
-}
-
-bool FilmLoopCastMember::isModified() {
-	if (_frames.size())
-		return true;
-
-	if (_initialRect.width() && _initialRect.height())
-		return true;
-
-	return false;
-}
-
-Common::Array<Channel> *FilmLoopCastMember::getSubChannels(Common::Rect &bbox, Channel *channel) {
-	Common::Rect widgetRect(bbox.width() ? bbox.width() : _initialRect.width(), bbox.height() ? bbox.height() : _initialRect.height());
-
-	_subchannels.clear();
-
-	if (channel->_filmLoopFrame >= _frames.size()) {
-		warning("Film loop frame %d requested, only %d available", channel->_filmLoopFrame, _frames.size());
-		return &_subchannels;
-	}
-
-	// get the list of sprite IDs for this frame
-	Common::Array<int> spriteIds;
-	for (Common::HashMap<int, Director::Sprite>::iterator iter = _frames[channel->_filmLoopFrame].sprites.begin(); iter != _frames[channel->_filmLoopFrame].sprites.end(); ++iter) {
-		spriteIds.push_back(iter->_key);
-	}
-	Common::sort(spriteIds.begin(), spriteIds.end());
-
-	// copy the sprites in order to the list
-	for (Common::Array<int>::iterator iter = spriteIds.begin(); iter != spriteIds.end(); ++iter) {
-		Sprite src = _frames[channel->_filmLoopFrame].sprites[*iter];
-		if (!src._cast)
-			continue;
-		// translate sprite relative to the global bounding box
-		int16 relX = (src._startPoint.x - _initialRect.left) * widgetRect.width() / _initialRect.width();
-		int16 relY = (src._startPoint.y - _initialRect.top) * widgetRect.height() / _initialRect.height();
-		int16 absX = relX + bbox.left;
-		int16 absY = relY + bbox.top;
-		int16 width = src._width * widgetRect.width() / _initialRect.width();
-		int16 height = src._height * widgetRect.height() / _initialRect.height();
-
-		Channel chan(&src);
-		chan._currentPoint = Common::Point(absX, absY);
-		chan._width = width;
-		chan._height = height;
-
-		_subchannels.push_back(chan);
-
-	}
-	// Initialise the widgets on all of the subchannels.
-	// This has to be done once the list has been constructed, otherwise
-	// the list grow operation will erase the widgets as they aren't
-	// part of the Channel assignment constructor.
-	for (auto &iter : _subchannels) {
-		iter.replaceWidget();
-	}
-
-	return &_subchannels;
-}
-
-void FilmLoopCastMember::loadFilmLoopData(Common::SeekableReadStreamEndian &stream) {
-	_initialRect = Common::Rect();
-	_frames.clear();
-
-	uint32 size = stream.readUint32BE();
-	if (debugChannelSet(5, kDebugLoading)) {
-		debugC(5, kDebugLoading, "SCVW body:");
-		uint32 pos = stream.pos();
-		stream.seek(0);
-		stream.hexdump(size);
-		stream.seek(pos);
-	}
-	uint16 channelSize = 16;
-	FilmLoopFrame newFrame;
-
-	while (stream.pos() < size) {
-		uint16 frameSize = stream.readUint16BE() - 2;
-		if (debugChannelSet(5, kDebugLoading)) {
-			debugC(5, kDebugLoading, "Frame entry:");
-			stream.hexdump(frameSize);
-		}
-
-		while (frameSize > 0) {
-			int msgWidth = stream.readByte() * 2;
-			int order = stream.readByte() * 2 - 0x20;
-			frameSize -= 2;
-			debugC(8, kDebugLoading, "Message: msgWidth %d, order %d", msgWidth, order);
-			if (debugChannelSet(8, kDebugLoading)) {
-				stream.hexdump(msgWidth);
-			}
-
-			int fieldPosition = order;
-			int finishPosition = order + msgWidth;
-			while (fieldPosition < finishPosition) {
-				int channel = (fieldPosition / channelSize);
-				int channelOffset = fieldPosition % channelSize;
-
-				Sprite sprite(nullptr);
-				sprite._movie = g_director->getCurrentMovie();
-				if (newFrame.sprites.contains(channel)) {
-					sprite = newFrame.sprites.getVal(channel);
-				}
-				sprite._spriteType = kCastMemberSprite;
-				sprite._puppet = 1;
-				sprite._stretch = 1;
-
-				switch (channelOffset) {
-				case kSpritePositionUnk1:
-					stream.readByte();
-					fieldPosition++;
-					break;
-				case kSpritePositionEnabled:
-					sprite._enabled = stream.readByte() != 0;
-					fieldPosition++;
-					break;
-				case kSpritePositionUnk2:
-					stream.readUint16BE();
-					fieldPosition += 2;
-					break;
-				case kSpritePositionFlags:
-					sprite._thickness = stream.readByte();
-					sprite._inkData = stream.readByte();
-					sprite._ink = static_cast<InkType>(sprite._inkData & 0x3f);
-
-					if (sprite._inkData & 0x40)
-						sprite._trails = 1;
-					else
-						sprite._trails = 0;
-
-					fieldPosition += 2;
-					break;
-				case kSpritePositionCastId:
-					sprite.setCast(CastMemberID(stream.readUint16(), 0));
-					fieldPosition += 2;
-					break;
-				case kSpritePositionY:
-					sprite._startPoint.y = stream.readUint16();
-					fieldPosition += 2;
-					break;
-				case kSpritePositionX:
-					sprite._startPoint.x = stream.readUint16();
-					fieldPosition += 2;
-					break;
-				case kSpritePositionWidth:
-					sprite._width = stream.readUint16();
-					fieldPosition += 2;
-					break;
-				case kSpritePositionHeight:
-					sprite._height = stream.readUint16();
-					fieldPosition += 2;
-					break;
-				default:
-					stream.readUint16BE();
-					fieldPosition += 2;
-					break;
-				}
-				newFrame.sprites.setVal(channel, sprite);
-			}
-
-			frameSize -= msgWidth;
-		}
-
-		for (Common::HashMap<int, Sprite>::iterator s = newFrame.sprites.begin(); s != newFrame.sprites.end(); ++s) {
-			debugC(5, kDebugLoading, "Sprite: channel %d, castId %s, bbox %d %d %d %d", s->_key,
-					s->_value._castId.asString().c_str(), s->_value._startPoint.x, s->_value._startPoint.y,
-					s->_value._width, s->_value._height);
-
-			Common::Point topLeft = s->_value._startPoint + s->_value.getRegistrationOffset();
-			Common::Rect spriteBbox(
-				topLeft.x,
-				topLeft.y,
-				topLeft.x + s->_value._width,
-				topLeft.y + s->_value._height
-			);
-			if (!((spriteBbox.width() == 0) && (spriteBbox.height() == 0))) {
-				if ((_initialRect.width() == 0) && (_initialRect.height() == 0)) {
-					_initialRect = spriteBbox;
-				} else {
-					_initialRect.extend(spriteBbox);
-				}
-			}
-			debugC(8, kDebugLoading, "New bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
-
-		}
-
-		_frames.push_back(newFrame);
-
-	}
-	debugC(5, kDebugLoading, "Full bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
-}
-
-void FilmLoopCastMember::loadFilmLoopDataV4(Common::SeekableReadStreamEndian &stream) {
-	_initialRect = Common::Rect();
-	_frames.clear();
-
-	uint32 size = stream.readUint32BE();
-	if (debugChannelSet(5, kDebugLoading)) {
-		debugC(5, kDebugLoading, "SCVW body:");
-		uint32 pos = stream.pos();
-		stream.seek(0);
-		stream.hexdump(size);
-		stream.seek(pos);
-	}
-	uint32 framesOffset = stream.readUint32BE();
-	if (debugChannelSet(5, kDebugLoading)) {
-		debugC(5, kDebugLoading, "SCVW header:");
-		stream.hexdump(framesOffset - 8);
-	}
-	stream.skip(6);
-	uint16 channelSize = stream.readUint16BE(); // should be 20!
-	stream.skip(framesOffset - 16);
-
-	FilmLoopFrame newFrame;
-
-	while (stream.pos() < size) {
-		uint16 frameSize = stream.readUint16BE() - 2;
-		if (debugChannelSet(5, kDebugLoading)) {
-			debugC(5, kDebugLoading, "Frame entry:");
-			stream.hexdump(frameSize);
-		}
-
-		while (frameSize > 0) {
-			uint16 msgWidth = stream.readUint16BE();
-			uint16 order = stream.readUint16BE();
-			frameSize -= 4;
-
-			int channel = (order / channelSize) - 1;
-			int channelOffset = order % channelSize;
-
-			Sprite sprite(nullptr);
-			sprite._movie = g_director->getCurrentMovie();
-			if (newFrame.sprites.contains(channel)) {
-				sprite = newFrame.sprites.getVal(channel);
-			}
-			debugC(8, kDebugLoading, "Message: msgWidth %d, channel %d, channelOffset %d", msgWidth, channel, channelOffset);
-			if (debugChannelSet(8, kDebugLoading)) {
-				stream.hexdump(msgWidth);
-			}
-			sprite._puppet = 1;
-			sprite._stretch = 1;
-
-			int fieldPosition = channelOffset;
-			int finishPosition = channelOffset + msgWidth;
-			while (fieldPosition < finishPosition) {
-				switch (fieldPosition) {
-				case kSpritePositionUnk1:
-					stream.readByte();
-					fieldPosition++;
-					break;
-				case kSpritePositionEnabled:
-					sprite._enabled = stream.readByte() != 0;
-					fieldPosition++;
-					break;
-				case kSpritePositionUnk2:
-					stream.readUint16BE();
-					fieldPosition += 2;
-					break;
-				case kSpritePositionFlags:
-					sprite._thickness = stream.readByte();
-					sprite._inkData = stream.readByte();
-					sprite._ink = static_cast<InkType>(sprite._inkData & 0x3f);
-
-					if (sprite._inkData & 0x40)
-						sprite._trails = 1;
-					else
-						sprite._trails = 0;
-
-					fieldPosition += 2;
-					break;
-				case kSpritePositionCastId:
-					sprite.setCast(CastMemberID(stream.readUint16(), 0));
-					fieldPosition += 2;
-					break;
-				case kSpritePositionY:
-					sprite._startPoint.y = stream.readUint16();
-					fieldPosition += 2;
-					break;
-				case kSpritePositionX:
-					sprite._startPoint.x = stream.readUint16();
-					fieldPosition += 2;
-					break;
-				case kSpritePositionWidth:
-					sprite._width = stream.readUint16();
-					fieldPosition += 2;
-					break;
-				case kSpritePositionHeight:
-					sprite._height = stream.readUint16();
-					fieldPosition += 2;
-					break;
-				default:
-					stream.readUint16BE();
-					fieldPosition += 2;
-					break;
-				}
-			}
-
-			frameSize -= msgWidth;
-
-			newFrame.sprites.setVal(channel, sprite);
-		}
-
-		for (Common::HashMap<int, Sprite>::iterator s = newFrame.sprites.begin(); s != newFrame.sprites.end(); ++s) {
-			debugC(5, kDebugLoading, "Sprite: channel %d, castId %s, bbox %d %d %d %d", s->_key,
-					s->_value._castId.asString().c_str(), s->_value._startPoint.x, s->_value._startPoint.y,
-					s->_value._width, s->_value._height);
-
-			Common::Point topLeft = s->_value._startPoint + s->_value.getRegistrationOffset();
-			Common::Rect spriteBbox(
-				topLeft.x,
-				topLeft.y,
-				topLeft.x + s->_value._width,
-				topLeft.y + s->_value._height
-			);
-			if (!((spriteBbox.width() == 0) && (spriteBbox.height() == 0))) {
-				if ((_initialRect.width() == 0) && (_initialRect.height() == 0)) {
-					_initialRect = spriteBbox;
-				} else {
-					_initialRect.extend(spriteBbox);
-				}
-			}
-			debugC(8, kDebugLoading, "New bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
-
-		}
-
-		_frames.push_back(newFrame);
-
-	}
-	debugC(5, kDebugLoading, "Full bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
-
-}
-
-Common::String FilmLoopCastMember::formatInfo() {
-	return Common::String::format(
-		"initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, frameCount: %d, subchannelCount: %d, enableSound: %d, looping: %d, crop: %d, center: %d",
-		_initialRect.width(), _initialRect.height(),
-		_initialRect.left, _initialRect.top,
-		_boundingRect.width(), _boundingRect.height(),
-		_boundingRect.left, _boundingRect.top,
-		_frames.size(), _subchannels.size(), _enableSound, _looping,
-		_crop, _center
-	);
-}
-
-/////////////////////////////////////
-// Sound
-/////////////////////////////////////
-
-SoundCastMember::SoundCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
-		: CastMember(cast, castId, stream) {
-	_type = kCastSound;
-	_audio = nullptr;
-	_looping = 0;
-}
-
-SoundCastMember::~SoundCastMember() {
-	if (_audio)
-		delete _audio;
-}
-
-Common::String SoundCastMember::formatInfo() {
-	return Common::String::format(
-		"looping: %d", _looping
-	);
-}
-
-/////////////////////////////////////
-// Text
-/////////////////////////////////////
-
-TextCastMember::TextCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version, uint8 flags1, bool asButton)
-		: CastMember(cast, castId, stream) {
-	_type = kCastText;
-
-	_borderSize = kSizeNone;
-	_gutterSize = kSizeNone;
-	_boxShadow = kSizeNone;
-	_buttonType = kTypeButton;
-	_editable = false;
-	_maxHeight = _textHeight = 0;
-
-	_bgcolor = 0;
-	_fgcolor = 0xff;
-
-	_textFlags = 0;
-	_scroll = 0;
-	_fontId = 1;
-	_fontSize = 12;
-	_textType = kTextTypeFixed;
-	_textAlign = kTextAlignLeft;
-	_textShadow = kSizeNone;
-	_textSlant = 0;
-	_bgpalinfo1 = _bgpalinfo2 = _bgpalinfo3 = 0;
-	_fgpalinfo1 = _fgpalinfo2 = _fgpalinfo3 = 0xff;
-
-	// seems like the line spacing is default to 1 in D4
-	_lineSpacing = g_director->getVersion() >= 400 ? 1 : 0;
-
-	if (version < kFileVer400) {
-		_flags1 = flags1; // region: 0 - auto, 1 - matte, 2 - disabled
-		_borderSize = static_cast<SizeType>(stream.readByte());
-		_gutterSize = static_cast<SizeType>(stream.readByte());
-		_boxShadow = static_cast<SizeType>(stream.readByte());
-		_textType = static_cast<TextType>(stream.readByte());
-		_textAlign = static_cast<TextAlignType>(stream.readUint16());
-		_bgpalinfo1 = stream.readUint16();
-		_bgpalinfo2 = stream.readUint16();
-		_bgpalinfo3 = stream.readUint16();
-
-		uint32 pad2;
-		uint16 pad3;
-		uint16 pad4 = 0;
-		uint16 totalTextHeight;
-
-		if (version < kFileVer300) {
-			pad2 = stream.readUint16();
-			if (pad2 != 0) { // In D2 there are values
-				warning("TextCastMember: pad2: %x", pad2);
-			}
-
-			_initialRect = Movie::readRect(stream);
-			pad3 = stream.readUint16();
-
-			_textShadow = static_cast<SizeType>(stream.readByte());
-			_textFlags = stream.readByte();
-			if (_textFlags & 0xf8)
-				warning("Unprocessed text cast flags: %x", _textFlags & 0xf8);
-
-			totalTextHeight = stream.readUint16();
-		} else {
-			pad2 = stream.readUint16();
-			_initialRect = Movie::readRect(stream);
-			pad3 = stream.readUint16();
-			_textFlags = stream.readUint16(); // 1: editable, 2: auto tab, 4: don't wrap
-			_editable = _textFlags & 0x1;
-			totalTextHeight = stream.readUint16();
-		}
-
-		debugC(2, kDebugLoading, "TextCastMember(): flags1: %d, border: %d gutter: %d shadow: %d textType: %d align: %04x",
-				_flags1, _borderSize, _gutterSize, _boxShadow, _textType, _textAlign);
-		debugC(2, kDebugLoading, "TextCastMember(): background rgb: 0x%04x 0x%04x 0x%04x, pad2: %x pad3: %d pad4: %d shadow: %d flags: %d totHeight: %d",
-				_bgpalinfo1, _bgpalinfo2, _bgpalinfo3, pad2, pad3, pad4, _textShadow, _textFlags, totalTextHeight);
-		if (debugChannelSet(2, kDebugLoading)) {
-			_initialRect.debugPrint(2, "TextCastMember(): rect:");
-		}
-	} else if (version >= kFileVer400 && version < kFileVer500) {
-		_flags1 = flags1;
-		_borderSize = static_cast<SizeType>(stream.readByte());
-		_gutterSize = static_cast<SizeType>(stream.readByte());
-		_boxShadow = static_cast<SizeType>(stream.readByte());
-		_textType = static_cast<TextType>(stream.readByte());
-		_textAlign = static_cast<TextAlignType>(stream.readSint16()); // this is because 'right' is -1? or should that be 255?
-		_bgpalinfo1 = stream.readUint16();
-		_bgpalinfo2 = stream.readUint16();
-		_bgpalinfo3 = stream.readUint16();
-		_scroll = stream.readUint16();
-
-		_fontId = 1; // this is in STXT
-
-		_initialRect = Movie::readRect(stream);
-		_maxHeight = stream.readUint16();
-		_textShadow = static_cast<SizeType>(stream.readByte());
-		_textFlags = stream.readByte(); // 1: editable, 2: auto tab 4: don't wrap
-		_editable = _textFlags & 0x1;
-
-		_textHeight = stream.readUint16();
-		_textSlant = 0;
-		debugC(2, kDebugLoading, "TextCastMember(): flags1: %d, border: %d gutter: %d shadow: %d textType: %d align: %04x",
-				_flags1, _borderSize, _gutterSize, _boxShadow, _textType, _textAlign);
-		debugC(2, kDebugLoading, "TextCastMember(): background rgb: 0x%04x 0x%04x 0x%04x, shadow: %d flags: %d textHeight: %d",
-				_bgpalinfo1, _bgpalinfo2, _bgpalinfo3, _textShadow, _textFlags, _textHeight);
-		if (debugChannelSet(2, kDebugLoading)) {
-			_initialRect.debugPrint(2, "TextCastMember(): rect:");
-		}
-	} else {
-		_fontId = 1;
-
-		stream.readUint32();
-		stream.readUint32();
-		stream.readUint32();
-		stream.readUint32();
-		uint16 skip = stream.readUint16();
-		for (int i = 0; i < skip; i++)
-			stream.readUint32();
-
-		stream.readUint32();
-		stream.readUint32();
-		stream.readUint32();
-		stream.readUint32();
-		stream.readUint32();
-		stream.readUint32();
-
-		_initialRect = Movie::readRect(stream);
-		_boundingRect = Movie::readRect(stream);
-
-		stream.readUint32();
-		stream.readUint16();
-		stream.readUint16();
-	}
-
-	if (asButton) {
-		_type = kCastButton;
-
-		if (version < kFileVer500) {
-			_buttonType = static_cast<ButtonType>(stream.readUint16BE() - 1);
-		} else {
-			warning("TextCastMember(): Attempting to initialize >D4 button castmember");
-			_buttonType = kTypeButton;
-		}
-	}
-
-	_bgcolor = g_director->_wm->findBestColor(_bgpalinfo1 & 0xff, _bgpalinfo2 & 0xff, _bgpalinfo3 & 0xff);
-
-	_modified = true;
-}
-
-void TextCastMember::setColors(uint32 *fgcolor, uint32 *bgcolor) {
-	if (fgcolor)
-		_fgcolor = *fgcolor;
-
-	if (bgcolor)
-		_bgcolor = *bgcolor;
-
-	// if we want to keep the format unchanged, then we need to modify _ftext as well
-	if (_widget)
-		((Graphics::MacText *)_widget)->setColors(_fgcolor, _bgcolor);
-	else
-		_modified = true;
-}
-
-Graphics::TextAlign TextCastMember::getAlignment() {
-	switch (_textAlign) {
-	case kTextAlignRight:
-		return Graphics::kTextAlignRight;
-	case kTextAlignCenter:
-		return Graphics::kTextAlignCenter;
-	case kTextAlignLeft:
-	default:
-		return Graphics::kTextAlignLeft;
-	}
-}
-
-void TextCastMember::setBackColor(uint32 bgCol) {
-	_bgcolor = bgCol;
-	_modified = true;
-}
-
-void TextCastMember::setForeColor(uint32 fgCol) {
-	_fgcolor = fgCol;
-	_modified = true;
-}
-
-void TextCastMember::importStxt(const Stxt *stxt) {
-	_fontId = stxt->_style.fontId;
-	_textSlant = stxt->_style.textSlant;
-	_fontSize = stxt->_style.fontSize;
-	_fgpalinfo1 = stxt->_style.r;
-	_fgpalinfo2 = stxt->_style.g;
-	_fgpalinfo3 = stxt->_style.b;
-	_ftext = stxt->_ftext;
-	_ptext = stxt->_ptext;
-	_rtext = stxt->_rtext;
-
-	// Rectifying _fontId in case of a fallback font
-	Graphics::MacFont macFont(_fontId, _fontSize, _textSlant);
-	g_director->_wm->_fontMan->getFont(&macFont);
-	_fontId = macFont.getId();
-}
-
-Graphics::MacWidget *TextCastMember::createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) {
-	Graphics::MacFont *macFont = new Graphics::MacFont(_fontId, _fontSize, _textSlant);
-	Graphics::MacWidget *widget = nullptr;
-	Common::Rect dims(bbox);
-
-	CastType type = _type;
-	ButtonType buttonType = _buttonType;
-
-	// WORKAROUND: In D2/D3 there can be text casts that have button
-	// information set in the sprite.
-	if (type == kCastText && isButtonSprite(spriteType)) {
-		type = kCastButton;
-		buttonType = ButtonType(spriteType - 8);
-	}
-
-	switch (type) {
-	case kCastText:
-		// for mactext, we can expand now, but we can't shrink. so we may pass the small size when we have adjustToFit text style
-		if (_textType == kTextTypeAdjustToFit) {
-			dims.right = MIN<int>(dims.right, dims.left + _initialRect.width());
-			dims.bottom = MIN<int>(dims.bottom, dims.top + _initialRect.height());
-		} else if (_textType == kTextTypeFixed){
-			// use initialRect to create widget for fixed style text, this maybe related to version.
-			dims.right = MAX<int>(dims.right, dims.left + _initialRect.width());
-			dims.bottom = MAX<int>(dims.bottom, dims.top + _initialRect.height());
-		}
-		widget = new Graphics::MacText(g_director->getCurrentWindow(), bbox.left, bbox.top, dims.width(), dims.height(), g_director->_wm, _ftext, macFont, getForeColor(), getBackColor(), _initialRect.width(), getAlignment(), _lineSpacing, _borderSize, _gutterSize, _boxShadow, _textShadow, _textType == kTextTypeFixed);
-		((Graphics::MacText *)widget)->setSelRange(g_director->getCurrentMovie()->_selStart, g_director->getCurrentMovie()->_selEnd);
-		((Graphics::MacText *)widget)->setEditable(channel->_sprite->_editable);
-		((Graphics::MacText *)widget)->draw();
-
-		// since we disable the ability of setActive in setEdtiable, then we need to set active widget manually
-		if (channel->_sprite->_editable) {
-			Graphics::MacWidget *activeWidget = g_director->_wm->getActiveWidget();
-			if (activeWidget == nullptr || !activeWidget->isEditable())
-				g_director->_wm->setActiveWidget(widget);
-		}
-		break;
-
-	case kCastButton:
-		// note that we use _initialRect for the dimensions of the button;
-		// the values provided in the sprite bounding box are ignored
-		widget = new Graphics::MacButton(Graphics::MacButtonType(buttonType), getAlignment(), g_director->getCurrentWindow(), bbox.left, bbox.top, _initialRect.width(), _initialRect.height(), g_director->_wm, _ftext, macFont, getForeColor(), g_director->_wm->_colorWhite);
-		widget->_focusable = true;
-
-		((Graphics::MacButton *)widget)->setHilite(_hilite);
-		((Graphics::MacButton *)widget)->setCheckBoxType(g_director->getCurrentMovie()->_checkBoxType);
-		((Graphics::MacButton *)widget)->draw();
-		break;
-
-	default:
-		break;
-	}
-
-	delete macFont;
-	return widget;
-}
-
-void TextCastMember::importRTE(byte *text) {
-	//assert(rteList.size() == 3);
-	//child0 is probably font data.
-	//child1 is the raw text.
-	_rtext = _ptext = _ftext = Common::String((char*)text);
-	//child2 is positional?
-}
-
-void TextCastMember::setRawText(const Common::String &text) {
-	// Do nothing if text did not change
-	if (_rtext.equals(text))
-		return;
-
-	_rtext = text;
-	_ptext = Common::U32String(text);
-
-	// If text has changed, use the cached formatting from first STXT in this castmember.
-	Common::U32String formatting = Common::String::format("\001\016%04x%02x%04x%04x%04x%04x", _fontId, _textSlant, _fontSize, _fgpalinfo1, _fgpalinfo2, _fgpalinfo3);
-	_ftext = formatting + _ptext;
-	_modified = true;
-}
-
-// D4 dictionary book said this is line spacing
-int TextCastMember::getTextHeight() {
-	if (_widget)
-		return ((Graphics::MacText *)_widget)->getLineSpacing();
-	else
-		return _lineSpacing;
-	return 0;
-}
-
-// this should be amend when we have some where using this function
-int TextCastMember::getTextSize() {
-	if (_widget)
-		return ((Graphics::MacText *)_widget)->getTextSize();
-	else
-		return _fontSize;
-	return 0;
-}
-
-Common::U32String TextCastMember::getText() {
-	return _ptext;
-}
-
-Common::String TextCastMember::getRawText() {
-	return _rtext;
-}
-
-void TextCastMember::setTextSize(int textSize) {
-	if (_widget) {
-		((Graphics::MacText *)_widget)->setTextSize(textSize);
-		((Graphics::MacText *)_widget)->draw();
-	} else {
-		_fontSize = textSize;
-		_modified = true;
-	}
-}
-
-void TextCastMember::updateFromWidget(Graphics::MacWidget *widget) {
-	if (widget && _type == kCastText) {
-		_ptext = ((Graphics::MacText *)widget)->getEditedString();
-	}
-}
-
-Common::String TextCastMember::formatInfo() {
-	Common::String format = formatStringForDump(_ptext.encode());
-
-	return Common::String::format(
-		"initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, foreColor: %d, backColor: %d, editable: %d, text: \"%s\"",
-		_initialRect.width(), _initialRect.height(),
-		_initialRect.left, _initialRect.top,
-		_boundingRect.width(), _boundingRect.height(),
-		_boundingRect.left, _boundingRect.top,
-		getForeColor(), getBackColor(),
-		_editable, format.c_str()
-	);
-
-}
-
-
-/////////////////////////////////////
-// Shape
-/////////////////////////////////////
-
-ShapeCastMember::ShapeCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
-		: CastMember(cast, castId, stream) {
-	_type = kCastShape;
-
-	byte unk1;
-
-	_ink = kInkTypeCopy;
-
-	if (version < kFileVer400) {
-		unk1 = stream.readByte();
-		_shapeType = static_cast<ShapeType>(stream.readByte());
-		_initialRect = Movie::readRect(stream);
-		_pattern = stream.readUint16BE();
-		// Normalize D2 and D3 colors from -128 ... 127 to 0 ... 255.
-		_fgCol = g_director->transformColor((128 + stream.readByte()) & 0xff);
-		_bgCol = g_director->transformColor((128 + stream.readByte()) & 0xff);
-		_fillType = stream.readByte();
-		_ink = static_cast<InkType>(_fillType & 0x3f);
-		_lineThickness = stream.readByte();
-		_lineDirection = stream.readByte();
-	} else if (version >= kFileVer400 && version < kFileVer500) {
-		unk1 = stream.readByte();
-		_shapeType = static_cast<ShapeType>(stream.readByte());
-		_initialRect = Movie::readRect(stream);
-		_pattern = stream.readUint16BE();
-		_fgCol = g_director->transformColor((uint8)stream.readByte());
-		_bgCol = g_director->transformColor((uint8)stream.readByte());
-		_fillType = stream.readByte();
-		_ink = static_cast<InkType>(_fillType & 0x3f);
-		_lineThickness = stream.readByte();
-		_lineDirection = stream.readByte();
-	} else {
-		stream.readByte(); // FIXME: Was this copied from D4 by mistake?
-		unk1 = stream.readByte();
-
-		_initialRect = Movie::readRect(stream);
-		_boundingRect = Movie::readRect(stream);
-
-		_shapeType = kShapeRectangle;
-		_pattern = 0;
-		_fgCol = _bgCol = 0;
-		_fillType = 0;
-		_lineThickness = 1;
-		_lineDirection = 0;
-	}
-	_modified = false;
-
-	debugC(3, kDebugLoading, "ShapeCastMember: unk1: %x type: %d pat: %d fg: %d bg: %d fill: %d thick: %d dir: %d",
-		unk1, _shapeType, _pattern, _fgCol, _bgCol, _fillType, _lineThickness, _lineDirection);
-
-	if (debugChannelSet(3, kDebugLoading))
-		_initialRect.debugPrint(0, "ShapeCastMember: rect:");
-}
-
-void ShapeCastMember::setBackColor(uint32 bgCol) {
-	_bgCol = bgCol;
-	_modified = true;
-}
-
-void ShapeCastMember::setForeColor(uint32 fgCol) {
-	_fgCol = fgCol;
-	_modified = true;
-}
-
-Common::String ShapeCastMember::formatInfo() {
-	return Common::String::format(
-		"initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, foreColor: %d, backColor: %d, shapeType: %d, pattern: %d, fillType: %d, lineThickness: %d, lineDirection: %d, ink: %d",
-		_initialRect.width(), _initialRect.height(),
-		_initialRect.left, _initialRect.top,
-		_boundingRect.width(), _boundingRect.height(),
-		_boundingRect.left, _boundingRect.top,
-		getForeColor(), getBackColor(),
-		_shapeType, _pattern, _fillType,
-		_lineThickness, _lineDirection, _ink
-	);
-}
-
-/////////////////////////////////////
-// Script
-/////////////////////////////////////
-
-ScriptCastMember::ScriptCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
-		: CastMember(cast, castId, stream) {
-	_type = kCastLingoScript;
-	_scriptType = kNoneScript;
-
-	if (debugChannelSet(5, kDebugLoading)) {
-		debugC(5, kDebugLoading, "ScriptCastMember::ScriptCastMember(): Contents");
-		stream.hexdump(stream.size());
-	}
-
-	if (version < kFileVer400) {
-		error("Unhandled Script cast");
-	} else if (version >= kFileVer400 && version < kFileVer600) {
-		byte unk1 = stream.readByte();
-		byte type = stream.readByte();
-
-		switch (type) {
-		case 1:
-			_scriptType = kScoreScript;
-			break;
-		case 3:
-			_scriptType = kMovieScript;
-			break;
-		default:
-			error("ScriptCastMember: Unprocessed script type: %d", type);
-		}
-
-		debugC(3, kDebugLoading, "CASt: Script type: %s (%d), unk1: %d", scriptType2str(_scriptType), type, unk1);
-
-		stream.readByte(); // There should be no more data
-		assert(stream.eos());
-	}
-}
-
-
-/////////////////////////////////////
-// RTE
-/////////////////////////////////////
-
-RTECastMember::RTECastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
-		: TextCastMember(cast, castId, stream, version) {
-
-	_type = kCastRTE;
-}
-
-void RTECastMember::loadChunks() {
-	//TODO: Actually load RTEs correctly, don't just make fake STXT.
-#if 0
-	Common::SeekableReadStream *rte1 = _movieArchive->getResource(res->children[child].tag, res->children[child].index);
-	byte *buffer = new byte[rte1->size() + 2];
-	rte1->read(buffer, rte1->size());
-	buffer[rte1->size()] = '\n';
-	buffer[rte1->size() + 1] = '\0';
-	_loadedText->getVal(id)->importRTE(buffer);
-
-	delete rte1;
-#endif
-}
-
-
-/////////////////////////////////////
-// Palette
-/////////////////////////////////////
-
-PaletteCastMember::PaletteCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
-	: CastMember(cast, castId, stream) {
-	_type = kCastPalette;
-	_palette = nullptr;
-}
-
-Common::String PaletteCastMember::formatInfo() {
-	Common::String result;
-	if (_palette) {
-		result = "data: ";
-		for (size_t i = 0; i < (size_t)_palette->length; i++) {
-			result += Common::String::format("%02X%02X%02X", _palette->palette[3 * i], _palette->palette[3 * i + 1], _palette->palette[3 * i + 2]);
-		}
-	}
-	return result;
-}
-
-} // End of namespace Director
diff --git a/engines/director/castmember.h b/engines/director/castmember.h
deleted file mode 100644
index 18fd6b99039..00000000000
--- a/engines/director/castmember.h
+++ /dev/null
@@ -1,417 +0,0 @@
-/* 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 <http://www.gnu.org/licenses/>.
- *
- */
-
-#ifndef DIRECTOR_CASTMEMBER_H
-#define DIRECTOR_CASTMEMBER_H
-
-#include "graphics/font.h"
-
-#include "director/archive.h"
-#include "director/sprite.h"
-#include "director/stxt.h"
-
-#include "director/lingo/lingo-object.h"
-
-namespace Graphics {
-struct Surface;
-class FloodFill;
-class MacText;
-class MacWindowManager;
-class MacButton;
-class MacWidget;
-}
-
-namespace Common {
-class SeekableReadStream;
-class SeekableReadStreamEndian;
-}
-
-namespace Image {
-class ImageDecoder;
-}
-
-namespace Video {
-class VideoDecoder;
-}
-
-namespace Director {
-
-class AudioDecoder;
-struct CastMemberInfo;
-class Channel;
-struct Picture;
-struct Resource;
-class Sprite;
-class Stxt;
-
-class CastMember : public Object<CastMember> {
-public:
-	CastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream);
-	CastMember(Cast *cast, uint16 castId);
-	virtual ~CastMember() {}
-
-	Cast *getCast() { return _cast; }
-	uint16 getID() { return _castId; }
-	CastMemberInfo *getInfo();
-
-	virtual bool isEditable() { return false; }
-	virtual void setEditable(bool editable) {}
-	virtual bool isModified() { return _modified; }
-	void setModified(bool modified);
-	virtual Graphics::MacWidget *createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) { return nullptr; }
-	virtual void updateWidget(Graphics::MacWidget *widget, Channel *channel) {}
-	virtual void updateFromWidget(Graphics::MacWidget *widget) {}
-	virtual Common::Rect getInitialRect() { return _initialRect; }
-
-	virtual void setColors(uint32 *fgcolor, uint32 *bgcolor) { return; }
-	virtual uint32 getForeColor() { return 0; }
-	virtual void setForeColor(uint32 fgCol) { return; }
-	virtual uint32 getBackColor() { return 0; }
-	virtual void setBackColor(uint32 bgCol) { return; }
-
-	bool hasProp(const Common::String &propName) override;
-	Datum getProp(const Common::String &propName) override;
-	bool setProp(const Common::String &propName, const Datum &value) override;
-	bool hasField(int field) override;
-	Datum getField(int field) override;
-	bool setField(int field, const Datum &value) override;
-
-	// release the control to widget, this happens when we are changing sprites. Because we are having the new cast member and the old one shall leave
-	void releaseWidget() { _widget = nullptr; }
-
-	virtual Common::String formatInfo() { return Common::String(); };
-
-	CastType _type;
-	Common::Rect _initialRect;
-	Common::Rect _boundingRect;
-	Common::Array<Resource> _children;
-
-	bool _hilite;
-	bool _erase;
-	int _purgePriority;
-	uint32 _size;
-	uint8 _flags1;
-
-protected:
-	Cast *_cast;
-	uint16 _castId;
-	// a link to the widget we created, we may use it later
-	Graphics::MacWidget *_widget;
-	bool _modified;
-	bool _isChanged;
-};
-
-class BitmapCastMember : public CastMember {
-public:
-	BitmapCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint32 castTag, uint16 version, uint8 flags1 = 0);
-	BitmapCastMember(Cast *cast, uint16 castId, Image::ImageDecoder *img, uint8 flags1 = 0);
-	~BitmapCastMember();
-	Graphics::MacWidget *createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) override;
-
-	bool isModified() override;
-	void createMatte(Common::Rect &bbox);
-	Graphics::Surface *getMatte(Common::Rect &bbox);
-	void copyStretchImg(Graphics::Surface *surface, const Common::Rect &bbox, const byte *pal = 0);
-
-	bool hasField(int field) override;
-	Datum getField(int field) override;
-	bool setField(int field, const Datum &value) override;
-
-	Common::String formatInfo() override;
-
-	PictureReference *getPicture() const;
-	void setPicture(PictureReference &picture);
-	void setPicture(Image::ImageDecoder &image, bool adjustSize);
-
-	Picture *_picture = nullptr;
-	Graphics::Surface *_ditheredImg;
-	Graphics::FloodFill *_matte;
-
-	uint16 _pitch;
-	uint16 _regX;
-	uint16 _regY;
-	uint16 _flags2;
-	uint16 _bytes;
-	int _clut;
-	int _ditheredTargetClut;
-
-	uint16 _bitsPerPixel;
-
-	uint32 _tag;
-	bool _noMatte;
-	bool _external;
-};
-
-class DigitalVideoCastMember : public CastMember {
-public:
-	DigitalVideoCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version);
-	~DigitalVideoCastMember();
-
-	bool isModified() override;
-	Graphics::MacWidget *createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) override;
-
-	bool loadVideo(Common::String path);
-	void startVideo(Channel *channel);
-	void stopVideo();
-	void rewindVideo();
-
-	uint getMovieCurrentTime();
-	uint getDuration();
-	uint getMovieTotalTime();
-	void seekMovie(int stamp);
-	void setStopTime(int stamp);
-	void setMovieRate(double rate);
-	void setFrameRate(int rate);
-
-	bool hasField(int field) override;
-	Datum getField(int field) override;
-	bool setField(int field, const Datum &value) override;
-
-	Common::String formatInfo() override;
-
-	Common::String _filename;
-
-	uint32 _vflags;
-	bool _looping;
-	bool _pausedAtStart;
-	bool _enableVideo;
-	bool _enableSound;
-	bool _crop;
-	bool _center;
-	bool _preload;
-	bool _showControls;
-	bool _directToStage;
-	bool _avimovie, _qtmovie;
-	FrameRateType _frameRateType;
-
-	uint16 _frameRate;
-	bool _getFirstFrame;
-	int _duration;
-
-	Video::VideoDecoder *_video;
-	Graphics::Surface *_lastFrame;
-
-	Channel *_channel;
-};
-
-
-struct FilmLoopFrame {
-	Common::HashMap<int, Sprite> sprites;
-};
-
-class FilmLoopCastMember : public CastMember {
-public:
-	FilmLoopCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version);
-	~FilmLoopCastMember();
-
-	bool isModified() override;
-	//Graphics::MacWidget *createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) override;
-
-	Common::Array<Channel> *getSubChannels(Common::Rect &bbox, Channel *channel);
-
-	void loadFilmLoopData(Common::SeekableReadStreamEndian &stream);
-	void loadFilmLoopDataV4(Common::SeekableReadStreamEndian &stream);
-
-	Common::String formatInfo() override;
-
-	bool _enableSound;
-	bool _looping;
-	bool _crop;
-	bool _center;
-
-	Common::Array<FilmLoopFrame> _frames;
-	Common::Array<Channel> _subchannels;
-};
-
-class MovieCastMember : public CastMember {
-public:
-	MovieCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version);
-
-	Common::String formatInfo() override;
-
-	uint32 _flags;
-	bool _looping;
-	bool _enableScripts;
-	bool _enableSound;
-	bool _crop;
-	bool _center;
-};
-
-class SoundCastMember : public CastMember {
-public:
-	SoundCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version);
-	~SoundCastMember();
-
-	Common::String formatInfo() override;
-
-	bool _looping;
-	AudioDecoder *_audio;
-};
-
-class ShapeCastMember : public CastMember {
-public:
-	ShapeCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version);
-	uint32 getForeColor() override { return _fgCol; }
-	uint32 getBackColor() override { return _bgCol; }
-	void setBackColor(uint32 bgCol) override;
-	void setForeColor(uint32 fgCol) override;
-
-	Common::String formatInfo() override;
-
-	ShapeType _shapeType;
-	uint16 _pattern;
-	byte _fillType;
-	byte _lineThickness;
-	byte _lineDirection;
-	InkType _ink;
-
-private:
-	uint32 _fgCol;
-	uint32 _bgCol;
-};
-
-class TextCastMember : public CastMember {
-public:
-	TextCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version, uint8 flags1 = 0, bool asButton = false);
-	void setColors(uint32 *fgcolor, uint32 *bgcolor) override;
-
-	Graphics::MacWidget *createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) override;
-
-	bool isEditable() override { return _editable; }
-	void setEditable(bool editable) override { _editable = editable; }
-	void updateFromWidget(Graphics::MacWidget *widget) override;
-	Graphics::TextAlign getAlignment();
-
-	uint32 getBackColor() override { return _bgcolor; }
-	void setBackColor(uint32 bgCol) override;
-	uint32 getForeColor() override { return _fgcolor; }
-	void setForeColor(uint32 fgCol) override;
-
-	bool hasField(int field) override;
-	Datum getField(int field) override;
-	bool setField(int field, const Datum &value) override;
-
-	bool hasChunkField(int field);
-	Datum getChunkField(int field, int start, int end);
-	bool setChunkField(int field, int start, int end, const Datum &value);
-
-	int getTextHeight();
-
-	int getTextSize();
-	void setTextSize(int textSize);
-
-	Common::String formatInfo() override;
-
-	SizeType _borderSize;
-	SizeType _gutterSize;
-	SizeType _boxShadow;
-	uint16 _maxHeight;
-	uint16 _textHeight;
-
-	uint32 _fontId;
-	uint16 _fontSize;
-	TextType _textType;
-	TextAlignType _textAlign;
-	SizeType _textShadow;
-	uint16 _scroll;
-	byte _textSlant;
-	byte _textFlags;
-	uint16 _bgpalinfo1, _bgpalinfo2, _bgpalinfo3;
-	uint16 _fgpalinfo1, _fgpalinfo2, _fgpalinfo3;
-	ButtonType _buttonType;
-	bool _editable;
-	int _lineSpacing;
-
-	Common::U32String _ftext;
-	Common::U32String _ptext;
-	Common::String _rtext;
-	void importStxt(const Stxt *stxt);
-	void importRTE(byte *text);
-
-	Common::U32String getText();
-	Common::String getRawText();
-	void setRawText(const Common::String &text);
-
-private:
-	uint32 _bgcolor;
-	uint32 _fgcolor;
-};
-
-class ScriptCastMember : public CastMember {
-public:
-	ScriptCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version);
-
-	ScriptType _scriptType;
-};
-
-class RTECastMember : public TextCastMember {
-public:
-	RTECastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version);
-
-	void loadChunks();
-};
-
-struct EditInfo {
-	Common::Rect rect;
-	int32 selStart;
-	int32 selEnd;
-	byte version;
-	byte rulerFlag;
-};
-
-struct CastMemberInfo {
-	bool autoHilite;
-	uint32 scriptId;
-	Common::String script;
-	Common::String name;
-	Common::String directory;
-	Common::String fileName;
-	Common::String type;
-	EditInfo scriptEditInfo;
-	FontStyle scriptStyle;
-	EditInfo textEditInfo;
-	Common::String modifiedBy;
-	Common::String comments;
-
-	CastMemberInfo() : autoHilite(false), scriptId(0) {}
-};
-
-struct Label {
-	Common::String comment;
-	Common::String name;
-	uint16 number;
-	Label(Common::String name1, uint16 number1, Common::String comment1) { name = name1; number = number1; comment = comment1;}
-};
-
-class PaletteCastMember : public CastMember {
-public:
-	PaletteCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version);
-	int getPaletteId() { return _palette ? _palette->id : 0; }
-	void activatePalette() { if (_palette) g_director->setPalette(_palette->id); }
-
-	Common::String formatInfo() override;
-
-	PaletteV4 *_palette;
-};
-
-} // End of namespace Director
-
-#endif
diff --git a/engines/director/castmember/bitmap.cpp b/engines/director/castmember/bitmap.cpp
new file mode 100644
index 00000000000..2bae5705fd6
--- /dev/null
+++ b/engines/director/castmember/bitmap.cpp
@@ -0,0 +1,591 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "graphics/surface.h"
+#include "graphics/macgui/macwidget.h"
+#include "image/image_decoder.h"
+
+#include "director/director.h"
+#include "director/cast.h"
+#include "director/movie.h"
+#include "director/picture.h"
+#include "director/score.h"
+#include "director/window.h"
+#include "director/castmember/bitmap.h"
+#include "director/lingo/lingo.h"
+#include "director/lingo/lingo-the.h"
+
+namespace Director {
+
+BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint32 castTag, uint16 version, uint8 flags1)
+		: CastMember(cast, castId, stream) {
+	_type = kCastBitmap;
+	_picture = nullptr;
+	_ditheredImg = nullptr;
+	_matte = nullptr;
+	_noMatte = false;
+	_bytes = 0;
+	_pitch = 0;
+	_flags2 = 0;
+	_regX = _regY = 0;
+	_clut = 0;
+	_ditheredTargetClut = 0;
+	_bitsPerPixel = 0;
+	_external = false;
+
+	if (version < kFileVer400) {
+		_flags1 = flags1;	// region: 0 - auto, 1 - matte, 2 - disabled
+
+		_bytes = stream.readUint16();
+		_initialRect = Movie::readRect(stream);
+		_boundingRect = Movie::readRect(stream);
+		_regY = stream.readUint16();
+		_regX = stream.readUint16();
+
+		if (_bytes & 0x8000) {
+			_bitsPerPixel = stream.readUint16();
+			_clut = stream.readSint16();
+			if (_clut <= 0) // builtin palette
+				_clut -= 1;
+		} else {
+			_bitsPerPixel = 1;
+			_clut = kClutSystemMac;
+		}
+
+		_pitch = _initialRect.width();
+		if (_pitch % 16)
+			_pitch += 16 - (_initialRect.width() % 16);
+
+		_pitch *= _bitsPerPixel;
+		_pitch >>= 3;
+
+	} else if (version >= kFileVer400 && version < kFileVer600) {
+		_flags1 = flags1;
+		_pitch = stream.readUint16();
+		_pitch &= 0x0fff;
+
+		_initialRect = Movie::readRect(stream);
+		_boundingRect = Movie::readRect(stream);
+		_regY = stream.readUint16();
+		_regX = stream.readUint16();
+
+		_bitsPerPixel = stream.readUint16();
+
+		if (stream.eos()) {
+			_bitsPerPixel = 0;
+		} else {
+			if (version >= kFileVer500) {
+				stream.readSint16(); // is this the castlib? was ff ff
+			}
+			_clut = stream.readSint16();
+			if (_clut <= 0) // builtin palette
+				_clut -= 1;
+			stream.readUint16();
+			/* uint16 unk1 = */ stream.readUint16();
+			stream.readUint16();
+
+			stream.readUint32();
+			stream.readUint32();
+
+			_flags2 = stream.readUint16();
+		}
+
+		if (_bitsPerPixel == 0)
+			_bitsPerPixel = 1;
+
+		int tail = 0;
+		byte buf[256];
+
+		while (!stream.eos()) {
+			byte c = stream.readByte();
+			if (tail < 256)
+				buf[tail] = c;
+			tail++;
+		}
+
+		if (tail)
+			warning("BUILDBOT: BitmapCastMember: %d bytes left", tail);
+
+		if (tail && debugChannelSet(2, kDebugLoading)) {
+			debug("BitmapCastMember: tail");
+			Common::hexdump(buf, tail);
+		}
+	}
+
+	_tag = castTag;
+}
+
+BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, Image::ImageDecoder *img, uint8 flags1)
+	: CastMember(cast, castId) {
+	_type = kCastBitmap;
+	_matte = nullptr;
+	_noMatte = false;
+	_bytes = 0;
+	if (img != nullptr) {
+		_picture = new Picture(*img);
+	}
+	_ditheredImg = nullptr;
+	_clut = -1;
+	_ditheredTargetClut = 0;
+	_initialRect = Common::Rect(0, 0, img->getSurface()->w, img->getSurface()->h);
+	_pitch = img->getSurface()->pitch;
+	_bitsPerPixel = img->getSurface()->format.bytesPerPixel * 8;
+	_regY = img->getSurface()->h / 2;
+	_regX = img->getSurface()->w / 2;
+	_flags1 = flags1;
+	_flags2 = 0;
+	_tag = 0;
+	_external = false;
+}
+
+BitmapCastMember::~BitmapCastMember() {
+	delete _picture;
+
+	if (_ditheredImg) {
+		_ditheredImg->free();
+		delete _ditheredImg;
+	}
+
+	if (_matte)
+		delete _matte;
+}
+
+Graphics::MacWidget *BitmapCastMember::createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) {
+	if (!_picture) {
+		warning("BitmapCastMember::createWidget: No picture");
+		return nullptr;
+	}
+
+	// skip creating widget when the bbox is not available, maybe we should create it using initialRect
+	if (!bbox.width() || !bbox.height())
+		return nullptr;
+
+	// Check if we need to dither the image
+	int dstBpp = g_director->_wm->_pixelformat.bytesPerPixel;
+	int srcBpp = _picture->_surface.format.bytesPerPixel;
+
+	const byte *pal = _picture->_palette;
+	bool previouslyDithered = _ditheredImg != nullptr;
+	if (_ditheredImg) {
+		_ditheredImg->free();
+		delete _ditheredImg;
+		_ditheredImg = nullptr;
+		_ditheredTargetClut = 0;
+	}
+
+	if (dstBpp == 1) {
+		if (srcBpp > 1
+		// At least early directors were not remapping 8bpp images. But in case it is
+		// needed, here is the code
+#if 0
+		|| (srcBpp == 1 &&
+			memcmp(g_director->_wm->getPalette(), _img->_palette, _img->_paletteSize))
+#endif
+			) {
+
+			_ditheredImg = _picture->_surface.convertTo(g_director->_wm->_pixelformat, _picture->_palette, _picture->_paletteColors, g_director->_wm->getPalette(), g_director->_wm->getPaletteSize());
+
+			pal = g_director->_wm->getPalette();
+		} else {
+			// Convert indexed image to indexed palette
+			Movie *movie = g_director->getCurrentMovie();
+			Cast *cast = movie->getCast();
+			Score *score = movie->getScore();
+			// Get the current score palette. Note that this is the ID of the palette in the list, not the cast member!
+			int currentPaletteId = score->resolvePaletteId(score->getCurrentPalette());
+			if (!currentPaletteId)
+				currentPaletteId = cast->_defaultPalette;
+			PaletteV4 *currentPalette = g_director->getPalette(currentPaletteId);
+			if (!currentPalette) {
+				currentPaletteId = kClutSystemMac;
+				currentPalette = g_director->getPalette(currentPaletteId);
+			}
+			int castPaletteId = score->resolvePaletteId(_clut);
+			// It is possible for Director to have saved an invalid ID in _clut;
+			// if this is the case, do no dithering.
+			if (!castPaletteId)
+				castPaletteId = currentPaletteId;
+
+			// Check if the palette is in the middle of a color fade event
+			bool isColorCycling = score->isPaletteColorCycling();
+
+			// First, check if the palettes are different
+			switch (_bitsPerPixel) {
+			// 1bpp - this is preconverted to 0x00 and 0xff, change nothing.
+			case 1:
+				break;
+			// 2bpp - convert to nearest using the standard 2-bit palette.
+			case 2:
+				{
+					const PaletteV4 &srcPal = g_director->getLoaded4Palette();
+					_ditheredImg = _picture->_surface.convertTo(g_director->_wm->_pixelformat, srcPal.palette, srcPal.length, currentPalette->palette, currentPalette->length, Graphics::kDitherNaive);
+				}
+				break;
+			// 4bpp - if using a builtin palette, use one of the corresponding 4-bit ones.
+			case 4:
+				{
+					const auto pals = g_director->getLoaded16Palettes();
+					// in D4 you aren't allowed to use custom palettes for 4-bit images, so uh...
+					// I guess default to the mac palette?
+					int palIndex = pals.contains(castPaletteId) ? castPaletteId : kClutSystemMac;
+					const PaletteV4 &srcPal = pals.getVal(palIndex);
+					_ditheredImg = _picture->_surface.convertTo(g_director->_wm->_pixelformat, srcPal.palette, srcPal.length, currentPalette->palette, currentPalette->length, Graphics::kDitherNaive);
+				}
+				break;
+			// 8bpp - if using a different palette, and we're not doing a color cycling operation, convert using nearest colour matching
+			case 8:
+				// Only redither 8-bit images if we have the flag set, or it is external
+				if (!movie->_remapPalettesWhenNeeded && !_external)
+					break;
+				if (_external || (castPaletteId != currentPaletteId && !isColorCycling)) {
+					const auto pals = g_director->getLoadedPalettes();
+					int palIndex = pals.contains(castPaletteId) ? castPaletteId : kClutSystemMac;
+					const PaletteV4 &srcPal = pals.getVal(palIndex);
+
+					// If it is an external image, use the included palette.
+					// For BMP images especially, they'll often have the right colors
+					// but in the wrong palette order.
+					const byte *palPtr = _external ? pal : srcPal.palette;
+					int palLength = _external ? _picture->getPaletteSize() : srcPal.length;
+					_ditheredImg = _picture->_surface.convertTo(g_director->_wm->_pixelformat, palPtr, palLength, currentPalette->palette, currentPalette->length, Graphics::kDitherNaive);
+				}
+				break;
+			default:
+				break;
+			}
+
+			if (_ditheredImg) {
+				debugC(4, kDebugImages, "BitmapCastMember::createWidget(): Dithering image from source palette %d to target palette %d", _clut, score->getCurrentPalette());
+				// Save the palette ID so we can check if a redraw is required
+				_ditheredTargetClut = currentPaletteId;
+
+				if (!_external) {
+					// Finally, the first and last colours in the palette are special. No matter what the palette remap
+					// does, we need to scrub those to be the same.
+					const Graphics::Surface *src = &_picture->_surface;
+					for (int y = 0; y < src->h; y++) {
+						for (int x = 0; x < src->w; x++) {
+							const int test = *(const byte *)src->getBasePtr(x, y);
+							if (test == 0 || test == (1 << _bitsPerPixel) - 1) {
+								*(byte *)_ditheredImg->getBasePtr(x, y) = test == 0 ? 0x00 : 0xff;
+							}
+						}
+					}
+				}
+			} else if (previouslyDithered) {
+				debugC(4, kDebugImages, "BitmapCastMember::createWidget(): Removed dithered image, score palette %d matches cast member", score->getCurrentPalette());
+			}
+
+		}
+	}
+
+	Graphics::MacWidget *widget = new Graphics::MacWidget(g_director->getCurrentWindow(), bbox.left, bbox.top, bbox.width(), bbox.height(), g_director->_wm, false);
+
+	// scale for drawing a different size sprite
+	copyStretchImg(widget->getSurface()->surfacePtr(), bbox, pal);
+
+	return widget;
+}
+
+void BitmapCastMember::copyStretchImg(Graphics::Surface *surface, const Common::Rect &bbox, const byte *pal) {
+	const Graphics::Surface *srcSurf;
+
+	if (_ditheredImg)
+		srcSurf = _ditheredImg;
+	else
+		srcSurf = &_picture->_surface;
+
+	if (bbox.width() != _initialRect.width() || bbox.height() != _initialRect.height()) {
+
+		int scaleX = SCALE_THRESHOLD * _initialRect.width() / bbox.width();
+		int scaleY = SCALE_THRESHOLD * _initialRect.height() / bbox.height();
+
+		for (int y = 0, scaleYCtr = 0; y < bbox.height(); y++, scaleYCtr += scaleY) {
+			if (g_director->_wm->_pixelformat.bytesPerPixel == 1) {
+				for (int x = 0, scaleXCtr = 0; x < bbox.width(); x++, scaleXCtr += scaleX) {
+					const byte *src = (const byte *)srcSurf->getBasePtr(scaleXCtr / SCALE_THRESHOLD, scaleYCtr / SCALE_THRESHOLD);
+					*(byte *)surface->getBasePtr(x, y) = *src;
+				}
+			} else {
+				for (int x = 0, scaleXCtr = 0; x < bbox.width(); x++, scaleXCtr += scaleX) {
+					const void *ptr = srcSurf->getBasePtr(scaleXCtr / SCALE_THRESHOLD, scaleYCtr / SCALE_THRESHOLD);
+					int32 color;
+
+					switch (srcSurf->format.bytesPerPixel) {
+					case 1:
+						{
+							color = *(const byte *)ptr * 3;
+							color = surface->format.RGBToColor(pal[color], pal[color + 1], pal[color + 2]);
+						}
+						break;
+					case 4:
+						color = *(const int32 *)ptr;
+						break;
+					default:
+						error("Unimplemented src bpp: %d", srcSurf->format.bytesPerPixel);
+					}
+
+					*(int32 *)surface->getBasePtr(x, y) = color;
+				}
+			}
+		}
+	} else {
+		surface->copyFrom(*srcSurf);
+	}
+
+	if (g_director->_debugDraw & kDebugDrawCast) {
+		surface->frameRect(Common::Rect(0, 0, surface->w, surface->h), g_director->_wm->_colorWhite);
+
+		const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont);
+		font->drawString(surface, Common::String::format("%d", _castId), 2, 2, 10, g_director->_wm->_colorWhite);
+	}
+}
+
+bool BitmapCastMember::isModified() {
+	if (CastMember::isModified()) {
+		// Let's us use "setChanged" when changing the picture through Lingo
+		return true;
+	}
+	// Check for palette changes.
+	// If a bitmap has a custom palette assigned to it, createWidget()
+	// will dither the image so that it fits within the current palette.
+	// When the score palette changes, we need to flag that the widget needs
+	// to be recreated.
+	if (_clut) {
+		Movie *movie = g_director->getCurrentMovie();
+		Cast *cast = movie->getCast();
+		Score *score = movie->getScore();
+		int currentPaletteId = score->resolvePaletteId(score->getCurrentPalette());
+		if (!currentPaletteId)
+			currentPaletteId = cast->_defaultPalette;
+		PaletteV4 *currentPalette = g_director->getPalette(currentPaletteId);
+		if (!currentPalette) {
+			currentPaletteId = kClutSystemMac;
+			currentPalette = g_director->getPalette(currentPaletteId);
+		}
+		int castPaletteId = score->resolvePaletteId(_clut);
+		if (!castPaletteId)
+			castPaletteId = cast->_defaultPalette;
+
+		if (currentPaletteId == castPaletteId) {
+			return _ditheredTargetClut != 0;
+		} else {
+			return _ditheredTargetClut != currentPaletteId;
+		}
+	}
+	return false;
+}
+
+void BitmapCastMember::createMatte(Common::Rect &bbox) {
+	// Like background trans, but all white pixels NOT ENCLOSED by coloured pixels
+	// are transparent
+	Graphics::Surface tmp;
+	tmp.create(bbox.width(), bbox.height(), g_director->_pixelformat);
+
+	copyStretchImg(&tmp, bbox);
+
+	_noMatte = true;
+
+	// Searching white color in the corners
+	uint32 whiteColor = 0;
+	bool colorFound = false;
+
+	if (g_director->_pixelformat.bytesPerPixel == 1) {
+		for (int y = 0; y < tmp.h; y++) {
+			for (int x = 0; x < tmp.w; x++) {
+				byte color = *(byte *)tmp.getBasePtr(x, y);
+
+				if (g_director->getPalette()[color * 3 + 0] == 0xff &&
+						g_director->getPalette()[color * 3 + 1] == 0xff &&
+						g_director->getPalette()[color * 3 + 2] == 0xff) {
+					whiteColor = color;
+					colorFound = true;
+					break;
+				}
+			}
+		}
+	} else {
+		whiteColor = g_director->_wm->_colorWhite;
+		colorFound = true;
+	}
+
+	if (!colorFound) {
+		debugC(1, kDebugImages, "BitmapCastMember::createMatte(): No white color for matte image");
+	} else {
+		delete _matte;
+
+		_matte = new Graphics::FloodFill(&tmp, whiteColor, 0, true);
+
+		for (int yy = 0; yy < tmp.h; yy++) {
+			_matte->addSeed(0, yy);
+			_matte->addSeed(tmp.w - 1, yy);
+		}
+
+		for (int xx = 0; xx < tmp.w; xx++) {
+			_matte->addSeed(xx, 0);
+			_matte->addSeed(xx, tmp.h - 1);
+		}
+
+		_matte->fillMask();
+		_noMatte = false;
+	}
+
+	tmp.free();
+}
+
+Graphics::Surface *BitmapCastMember::getMatte(Common::Rect &bbox) {
+	// Lazy loading of mattes
+	if (!_matte && !_noMatte) {
+		createMatte(bbox);
+	}
+
+	// check for the scale matte
+	Graphics::Surface *surface = _matte ? _matte->getMask() : nullptr;
+	if (surface && (surface->w != bbox.width() || surface->h != bbox.height())) {
+		createMatte(bbox);
+	}
+
+	return _matte ? _matte->getMask() : nullptr;
+}
+
+Common::String BitmapCastMember::formatInfo() {
+	return Common::String::format(
+		"initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, foreColor: %d, backColor: %d, regX: %d, regY: %d, pitch: %d, bitsPerPixel: %d, palette: %d",
+		_initialRect.width(), _initialRect.height(),
+		_initialRect.left, _initialRect.top,
+		_boundingRect.width(), _boundingRect.height(),
+		_boundingRect.left, _boundingRect.top,
+		getForeColor(), getBackColor(),
+		_regX, _regY, _pitch, _bitsPerPixel, _clut
+	);
+}
+
+PictureReference *BitmapCastMember::getPicture() const {
+	auto picture = new PictureReference;
+
+	// Not sure if we can make the assumption that the owning
+	// BitmapCastMember will live as long as any reference,
+	// so we'll make a copy of the Picture.
+	picture->_picture = new Picture(*_picture);
+
+	return picture;
+}
+
+void BitmapCastMember::setPicture(PictureReference &picture) {
+	delete _picture;
+	_picture = new Picture(*picture._picture);
+
+	// Force redither
+	delete _ditheredImg;
+	_ditheredImg = nullptr;
+
+	// Make sure we get redrawn
+	setModified(true);
+	// TODO: Should size be adjusted?
+}
+
+void BitmapCastMember::setPicture(Image::ImageDecoder &image, bool adjustSize) {
+	delete _picture;
+	_picture = new Picture(image);
+	if (adjustSize) {
+		auto surf = image.getSurface();
+		_size = surf->pitch * surf->h + _picture->getPaletteSize();
+	}
+	// Make sure we get redrawn
+	setModified(true);
+}
+
+bool BitmapCastMember::hasField(int field) {
+	switch (field) {
+	case kTheDepth:
+	case kTheRegPoint:
+	case kThePalette:
+	case kThePicture:
+		return true;
+	default:
+		break;
+	}
+	return CastMember::hasField(field);
+}
+
+Datum BitmapCastMember::getField(int field) {
+	Datum d;
+
+	switch (field) {
+	case kTheDepth:
+		d = _bitsPerPixel;
+		break;
+	case kTheRegPoint:
+		d.type = POINT;
+		d.u.farr = new FArray;
+		d.u.farr->arr.push_back(_regX);
+		d.u.farr->arr.push_back(_regY);
+		break;
+	case kThePalette:
+		d = _clut;
+		break;
+	case kThePicture:
+		d.type = PICTUREREF;
+		d.u.picture = getPicture();
+		break;
+	default:
+		d = CastMember::getField(field);
+	}
+
+	return d;
+}
+
+bool BitmapCastMember::setField(int field, const Datum &d) {
+	switch (field) {
+	case kTheDepth:
+		warning("BitmapCastMember::setField(): Attempt to set read-only field %s of cast %d", g_lingo->field2str(field), _castId);
+		return false;
+	case kTheRegPoint:
+		if (d.type == POINT || (d.type == ARRAY && d.u.farr->arr.size() >= 2)) {
+			Score *score = g_director->getCurrentMovie()->getScore();
+			score->invalidateRectsForMember(this);
+			_regX = d.u.farr->arr[0].asInt();
+			_regY = d.u.farr->arr[1].asInt();
+			_modified = true;
+		} else {
+			warning("BitmapCastMember::setField(): Wrong Datum type %d for kTheRegPoint", d.type);
+			return false;
+		}
+		return true;
+	case kThePalette:
+		_clut = d.asInt();
+		return true;
+	case kThePicture:
+		if (d.type == PICTUREREF && d.u.picture != nullptr) {
+			setPicture(*d.u.picture);
+			return true;
+		} else {
+			warning("BitmapCastMember::setField(): Wrong Datum type %d for kThePicture (or nullptr)", d.type);
+		}
+		return false;
+	default:
+		break;
+	}
+
+	return CastMember::setField(field, d);
+}
+
+} // End of namespace Director
diff --git a/engines/director/castmember/bitmap.h b/engines/director/castmember/bitmap.h
new file mode 100644
index 00000000000..ae8808983fe
--- /dev/null
+++ b/engines/director/castmember/bitmap.h
@@ -0,0 +1,76 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef DIRECTOR_CASTMEMBER_BITMAP_H
+#define DIRECTOR_CASTMEMBER_BITMAP_H
+
+#include "director/castmember/castmember.h"
+
+namespace Image {
+class ImageDecoder;
+}
+
+namespace Director {
+
+class BitmapCastMember : public CastMember {
+public:
+	BitmapCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint32 castTag, uint16 version, uint8 flags1 = 0);
+	BitmapCastMember(Cast *cast, uint16 castId, Image::ImageDecoder *img, uint8 flags1 = 0);
+	~BitmapCastMember();
+	Graphics::MacWidget *createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) override;
+
+	bool isModified() override;
+	void createMatte(Common::Rect &bbox);
+	Graphics::Surface *getMatte(Common::Rect &bbox);
+	void copyStretchImg(Graphics::Surface *surface, const Common::Rect &bbox, const byte *pal = 0);
+
+	bool hasField(int field) override;
+	Datum getField(int field) override;
+	bool setField(int field, const Datum &value) override;
+
+	Common::String formatInfo() override;
+
+	PictureReference *getPicture() const;
+	void setPicture(PictureReference &picture);
+	void setPicture(Image::ImageDecoder &image, bool adjustSize);
+
+	Picture *_picture = nullptr;
+	Graphics::Surface *_ditheredImg;
+	Graphics::FloodFill *_matte;
+
+	uint16 _pitch;
+	uint16 _regX;
+	uint16 _regY;
+	uint16 _flags2;
+	uint16 _bytes;
+	int _clut;
+	int _ditheredTargetClut;
+
+	uint16 _bitsPerPixel;
+
+	uint32 _tag;
+	bool _noMatte;
+	bool _external;
+};
+
+} // End of namespace Director
+
+#endif
diff --git a/engines/director/castmember/castmember.cpp b/engines/director/castmember/castmember.cpp
new file mode 100644
index 00000000000..0c7692c8bf6
--- /dev/null
+++ b/engines/director/castmember/castmember.cpp
@@ -0,0 +1,241 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "director/director.h"
+#include "director/cast.h"
+#include "director/castmember/castmember.h"
+#include "director/lingo/lingo.h"
+#include "director/lingo/lingo-the.h"
+
+namespace Director {
+
+CastMember::CastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream) : Object<CastMember>("CastMember") {
+	_type = kCastTypeNull;
+	_cast = cast;
+	_castId = castId;
+	_hilite = false;
+	_purgePriority = 3;
+	_size = stream.size();
+	_flags1 = 0;
+
+	_modified = true;
+	_isChanged = false;
+
+	_objType = kCastMemberObj;
+
+	_widget = nullptr;
+	_erase = false;
+}
+
+CastMember::CastMember(Cast *cast, uint16 castId) : Object<CastMember>("CastMember") {
+	_type = kCastTypeNull;
+	_cast = cast;
+	_castId = castId;
+	_hilite = false;
+	_purgePriority = 3;
+	_size = 0;
+	_flags1 = 0;
+
+	_modified = true;
+	_isChanged = false;
+
+	_objType = kCastMemberObj;
+
+	_widget = nullptr;
+	_erase = false;
+}
+
+void CastMember::setModified(bool modified) {
+	_modified = modified;
+	if (modified)
+		_isChanged = true;
+}
+
+bool CastMember::hasProp(const Common::String &propName) {
+	Common::String fieldName = Common::String::format("%d%s", kTheCast, propName.c_str());
+	return g_lingo->_theEntityFields.contains(fieldName) && hasField(g_lingo->_theEntityFields[fieldName]->field);
+}
+
+Datum CastMember::getProp(const Common::String &propName) {
+	Common::String fieldName = Common::String::format("%d%s", kTheCast, propName.c_str());
+	if (g_lingo->_theEntityFields.contains(fieldName)) {
+		return getField(g_lingo->_theEntityFields[fieldName]->field);
+	}
+
+	warning("CastMember::getProp: unknown property '%s'", propName.c_str());
+	return Datum();
+}
+
+bool CastMember::setProp(const Common::String &propName, const Datum &value) {
+	Common::String fieldName = Common::String::format("%d%s", kTheCast, propName.c_str());
+	if (g_lingo->_theEntityFields.contains(fieldName)) {
+		return setField(g_lingo->_theEntityFields[fieldName]->field, value);
+	}
+
+	warning("CastMember::setProp: unknown property '%s'", propName.c_str());
+	return false;
+}
+
+bool CastMember::hasField(int field) {
+	switch (field) {
+	case kTheBackColor:
+	case kTheCastType:
+	case kTheFileName:
+	case kTheForeColor:
+	case kTheHeight:
+	case kTheLoaded:
+	case kTheModified:
+	case kTheName:
+	case kTheNumber:
+	case kTheRect:
+	case kThePurgePriority:
+	case kTheScriptText:
+	case kTheSize:
+	case kTheWidth:
+		return true;
+	default:
+		break;
+	}
+	return false;
+}
+
+Datum CastMember::getField(int field) {
+	Datum d;
+
+	CastMemberInfo *castInfo = _cast->getCastMemberInfo(_castId);
+	if (!castInfo)
+		warning("CastMember::getField(): CastMember info for %d not found", _castId);
+
+	switch (field) {
+	case kTheBackColor:
+		d = (int)getBackColor();
+		break;
+	case kTheCastType:
+		d.type = SYMBOL;
+		d.u.s = new Common::String(castType2str(_type));
+		break;
+	case kTheFileName:
+		if (castInfo)
+			d = Datum(castInfo->directory + g_director->_dirSeparator + castInfo->fileName);
+		break;
+	case kTheForeColor:
+		d = (int)getForeColor();
+		break;
+	case kTheHeight:
+		d = _cast->getCastMemberInitialRect(_castId).height();
+		break;
+	case kTheLoaded:
+		d = 1; // Not loaded handled in Lingo::getTheCast
+		break;
+	case kTheModified:
+		d = (int)_isChanged;
+		break;
+	case kTheName:
+		if (castInfo)
+			d = Datum(castInfo->name);
+		break;
+	case kTheNumber:
+		d = _castId;
+		break;
+	case kTheRect:
+		// not sure get the initial rect would be fine to castmember
+		d = Datum(_cast->getCastMember(_castId)->_initialRect);
+		break;
+	case kThePurgePriority:
+		d = _purgePriority;
+		break;
+	case kTheScriptText:
+		if (castInfo)
+			d = Datum(castInfo->script);
+		break;
+	case kTheSize:
+		d = (int)_size;
+		break;
+	case kTheWidth:
+		d = _cast->getCastMemberInitialRect(_castId).width();
+		break;
+	default:
+		warning("CastMember::getField(): Unprocessed getting field \"%s\" of cast %d", g_lingo->field2str(field), _castId);
+	//TODO find out about String fields
+	}
+
+	return d;
+}
+
+bool CastMember::setField(int field, const Datum &d) {
+	CastMemberInfo *castInfo = _cast->getCastMemberInfo(_castId);
+
+	switch (field) {
+	case kTheBackColor:
+		_cast->getCastMember(_castId)->setBackColor(d.asInt());
+		return true;
+	case kTheCastType:
+		warning("BUILDBOT: CastMember::setField(): Attempt to set read-only field %s of cast %d", g_lingo->entity2str(field), _castId);
+		return false;
+	case kTheFileName:
+		if (!castInfo) {
+			warning("CastMember::setField(): CastMember info for %d not found", _castId);
+			return false;
+		}
+		castInfo->fileName = d.asString();
+		return true;
+	case kTheForeColor:
+		_cast->getCastMember(_castId)->setForeColor(d.asInt());
+		return true;
+	case kTheHeight:
+		warning("BUILDBOT: CastMember::setField(): Attempt to set read-only field \"%s\" of cast %d", g_lingo->field2str(field), _castId);
+		return false;
+	case kTheName:
+		if (!castInfo) {
+			warning("CastMember::setField(): CastMember info for %d not found", _castId);
+			return false;
+		}
+		castInfo->name = d.asString();
+		return true;
+	case kTheRect:
+		warning("CastMember::setField(): Attempt to set read-only field \"%s\" of cast %d", g_lingo->field2str(field), _castId);
+		return false;
+	case kThePurgePriority:
+		_purgePriority = CLIP<int>(d.asInt(), 0, 3);
+		return true;
+	case kTheScriptText:
+		if (!castInfo) {
+			warning("CastMember::setField(): CastMember info for %d not found", _castId);
+			return false;
+		}
+		_cast->_lingoArchive->replaceCode(*d.u.s, kCastScript, _castId);
+		castInfo->script = d.asString();
+		return true;
+	case kTheWidth:
+		warning("BUILDBOT: CastMember::setField(): Attempt to set read-only field \"%s\" of cast %d", g_lingo->field2str(field), _castId);
+		return false;
+	default:
+		warning("CastMember::setField(): Unprocessed setting field \"%s\" of cast %d", g_lingo->field2str(field), _castId);
+	}
+
+	return false;
+}
+
+CastMemberInfo *CastMember::getInfo() {
+	return _cast->getCastMemberInfo(_castId);
+}
+
+} // End of namespace Director
diff --git a/engines/director/castmember/castmember.h b/engines/director/castmember/castmember.h
new file mode 100644
index 00000000000..f5e8ec22948
--- /dev/null
+++ b/engines/director/castmember/castmember.h
@@ -0,0 +1,132 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef DIRECTOR_CASTMEMBER_CASTMEMBER_H
+#define DIRECTOR_CASTMEMBER_CASTMEMBER_H
+
+#include "graphics/font.h"
+
+#include "director/archive.h"
+#include "director/sprite.h"
+#include "director/stxt.h"
+
+#include "director/lingo/lingo-object.h"
+
+namespace Graphics {
+class MacWidget;
+}
+
+namespace Common {
+class SeekableReadStream;
+class SeekableReadStreamEndian;
+}
+
+namespace Director {
+
+struct CastMemberInfo;
+class Channel;
+struct Resource;
+
+class CastMember : public Object<CastMember> {
+public:
+	CastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream);
+	CastMember(Cast *cast, uint16 castId);
+	virtual ~CastMember() {}
+
+	Cast *getCast() { return _cast; }
+	uint16 getID() { return _castId; }
+	CastMemberInfo *getInfo();
+
+	virtual bool isEditable() { return false; }
+	virtual void setEditable(bool editable) {}
+	virtual bool isModified() { return _modified; }
+	void setModified(bool modified);
+	virtual Graphics::MacWidget *createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) { return nullptr; }
+	virtual void updateWidget(Graphics::MacWidget *widget, Channel *channel) {}
+	virtual void updateFromWidget(Graphics::MacWidget *widget) {}
+	virtual Common::Rect getInitialRect() { return _initialRect; }
+
+	virtual void setColors(uint32 *fgcolor, uint32 *bgcolor) { return; }
+	virtual uint32 getForeColor() { return 0; }
+	virtual void setForeColor(uint32 fgCol) { return; }
+	virtual uint32 getBackColor() { return 0; }
+	virtual void setBackColor(uint32 bgCol) { return; }
+
+	bool hasProp(const Common::String &propName) override;
+	Datum getProp(const Common::String &propName) override;
+	bool setProp(const Common::String &propName, const Datum &value) override;
+	bool hasField(int field) override;
+	Datum getField(int field) override;
+	bool setField(int field, const Datum &value) override;
+
+	// release the control to widget, this happens when we are changing sprites. Because we are having the new cast member and the old one shall leave
+	void releaseWidget() { _widget = nullptr; }
+
+	virtual Common::String formatInfo() { return Common::String(); };
+
+	CastType _type;
+	Common::Rect _initialRect;
+	Common::Rect _boundingRect;
+	Common::Array<Resource> _children;
+
+	bool _hilite;
+	bool _erase;
+	int _purgePriority;
+	uint32 _size;
+	uint8 _flags1;
+
+protected:
+	Cast *_cast;
+	uint16 _castId;
+	// a link to the widget we created, we may use it later
+	Graphics::MacWidget *_widget;
+	bool _modified;
+	bool _isChanged;
+};
+
+struct EditInfo {
+	Common::Rect rect;
+	int32 selStart;
+	int32 selEnd;
+	byte version;
+	byte rulerFlag;
+};
+
+struct CastMemberInfo {
+	bool autoHilite;
+	uint32 scriptId;
+	Common::String script;
+	Common::String name;
+	Common::String directory;
+	Common::String fileName;
+	Common::String type;
+	EditInfo scriptEditInfo;
+	FontStyle scriptStyle;
+	EditInfo textEditInfo;
+	Common::String modifiedBy;
+	Common::String comments;
+
+	CastMemberInfo() : autoHilite(false), scriptId(0) {}
+};
+
+} // End of namespace Director
+
+#endif
diff --git a/engines/director/castmember/digitalvideo.cpp b/engines/director/castmember/digitalvideo.cpp
new file mode 100644
index 00000000000..d299d87502f
--- /dev/null
+++ b/engines/director/castmember/digitalvideo.cpp
@@ -0,0 +1,442 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "graphics/surface.h"
+#include "graphics/macgui/macwidget.h"
+
+#include "video/avi_decoder.h"
+#include "video/qt_decoder.h"
+
+#include "director/director.h"
+#include "director/cast.h"
+#include "director/channel.h"
+#include "director/movie.h"
+#include "director/window.h"
+#include "director/castmember/digitalvideo.h"
+#include "director/lingo/lingo.h"
+#include "director/lingo/lingo-the.h"
+
+namespace Director {
+
+DigitalVideoCastMember::DigitalVideoCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
+		: CastMember(cast, castId, stream) {
+	_type = kCastDigitalVideo;
+	_video = nullptr;
+	_lastFrame = nullptr;
+	_channel = nullptr;
+
+	_getFirstFrame = false;
+	_duration = 0;
+
+	_initialRect = Movie::readRect(stream);
+	_vflags = stream.readUint32();
+	_frameRate = (_vflags >> 24) & 0xff;
+
+	_frameRateType = kFrameRateDefault;
+	if (_vflags & 0x0800) {
+		_frameRateType = (FrameRateType)((_vflags & 0x3000) >> 12);
+	}
+	_qtmovie = _vflags & 0x8000;
+	_avimovie = _vflags & 0x4000;
+	_preload = _vflags & 0x0400;
+	_enableVideo = !(_vflags & 0x0200);
+	_pausedAtStart = _vflags & 0x0100;
+	_showControls = _vflags & 0x40;
+	_directToStage = _vflags & 0x20;
+	_looping = _vflags & 0x10;
+	_enableSound = _vflags & 0x08;
+	_crop = !(_vflags & 0x02);
+	_center = _vflags & 0x01;
+
+	if (debugChannelSet(2, kDebugLoading))
+		_initialRect.debugPrint(2, "DigitalVideoCastMember(): rect:");
+
+	debugC(2, kDebugLoading, "DigitalVideoCastMember(): flags: (%d 0x%04x)", _vflags, _vflags);
+
+	debugC(2, kDebugLoading, "_frameRate: %d", _frameRate);
+	debugC(2, kDebugLoading, "_frameRateType: %d, _preload: %d, _enableVideo %d, _pausedAtStart %d",
+			_frameRateType, _preload, _enableVideo, _pausedAtStart);
+	debugC(2, kDebugLoading, "_showControls: %d, _looping: %d, _enableSound: %d, _crop %d, _center: %d, _directToStage: %d",
+			_showControls, _looping, _enableSound, _crop, _center, _directToStage);
+	debugC(2, kDebugLoading, "_avimovie: %d, _qtmovie: %d", _avimovie, _qtmovie);
+}
+
+DigitalVideoCastMember::~DigitalVideoCastMember() {
+	if (_lastFrame) {
+		_lastFrame->free();
+		delete _lastFrame;
+	}
+
+	if (_video)
+		delete _video;
+}
+
+bool DigitalVideoCastMember::loadVideo(Common::String path) {
+	// TODO: detect file type (AVI, QuickTime, FLIC) based on magic number,
+	// insert the right video decoder
+
+	if (_video)
+		delete _video;
+
+	_filename = path;
+	_video = new Video::QuickTimeDecoder();
+
+	Common::String path1 = pathMakeRelative(path);
+
+	debugC(2, kDebugLoading | kDebugImages, "Loading video %s -> %s", path.c_str(), path1.c_str());
+	bool result = _video->loadFile(Common::Path(path1, g_director->_dirSeparator));
+	if (!result) {
+		delete _video;
+		_video = new Video::AVIDecoder();
+		result = _video->loadFile(Common::Path(path1, g_director->_dirSeparator));
+		if (!result) {
+		    warning("DigitalVideoCastMember::loadVideo(): format not supported, skipping");
+		    delete _video;
+		    _video = nullptr;
+		}
+	}
+
+	if (result && g_director->_pixelformat.bytesPerPixel == 1) {
+		// Director supports playing back RGB and paletted video in 256 colour mode.
+		// In both cases they are dithered to match the Director palette.
+		byte palette[256 * 3];
+		g_system->getPaletteManager()->grabPalette(palette, 0, 256);
+		_video->setDitheringPalette(palette);
+	}
+
+	return result;
+}
+
+bool DigitalVideoCastMember::isModified() {
+	if (!_video || !_video->isVideoLoaded())
+		return true;
+
+	if (_getFirstFrame)
+		return true;
+
+	if (_channel->_movieRate == 0.0)
+		return false;
+
+	return _video->needsUpdate();
+}
+
+void DigitalVideoCastMember::startVideo(Channel *channel) {
+	_channel = channel;
+
+	if (!_video || !_video->isVideoLoaded()) {
+		warning("DigitalVideoCastMember::startVideo: No video %s", !_video ? "decoder" : "loaded");
+		return;
+	}
+
+	if (_pausedAtStart) {
+		_getFirstFrame = true;
+	} else {
+		if (_channel->_movieRate == 0.0)
+			_channel->_movieRate = 1.0;
+	}
+
+	if (_video->isPlaying())
+		_video->rewind();
+	else
+		_video->start();
+
+	debugC(2, kDebugImages, "STARTING VIDEO %s", _filename.c_str());
+
+	if (_channel->_stopTime == 0)
+		_channel->_stopTime = getMovieTotalTime();
+
+	_duration = getMovieTotalTime();
+}
+
+void DigitalVideoCastMember::stopVideo() {
+	if (!_video || !_video->isVideoLoaded()) {
+		warning("DigitalVideoCastMember::stopVideo: No video decoder");
+		return;
+	}
+
+	_video->stop();
+
+	debugC(2, kDebugImages, "STOPPING VIDEO %s", _filename.c_str());
+}
+
+void DigitalVideoCastMember::rewindVideo() {
+	if (!_video || !_video->isVideoLoaded()) {
+		warning("DigitalVideoCastMember::rewindVideo: No video decoder");
+		return;
+	}
+
+	_video->rewind();
+
+	debugC(2, kDebugImages, "REWINDING VIDEO %s", _filename.c_str());
+}
+
+Graphics::MacWidget *DigitalVideoCastMember::createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) {
+	Graphics::MacWidget *widget = new Graphics::MacWidget(g_director->getCurrentWindow(), bbox.left, bbox.top, bbox.width(), bbox.height(), g_director->_wm, false);
+
+	_channel = channel;
+
+	if (!_video || !_video->isVideoLoaded()) {
+		warning("DigitalVideoCastMember::createWidget: No video decoder");
+		delete widget;
+
+		return nullptr;
+	}
+
+	// Do not render stopped videos
+	if (_channel->_movieRate == 0.0 && !_getFirstFrame && _lastFrame) {
+		widget->getSurface()->blitFrom(*_lastFrame);
+
+		return widget;
+	}
+
+	const Graphics::Surface *frame = _video->decodeNextFrame();
+
+	debugC(1, kDebugImages, "Video time: %d  rate: %f", _channel->_movieTime, _channel->_movieRate);
+
+	if (frame) {
+		if (_lastFrame) {
+			_lastFrame->free();
+			delete _lastFrame;
+		}
+
+		_lastFrame = frame->convertTo(g_director->_pixelformat, g_director->getPalette());
+	}
+	if (_lastFrame)
+		widget->getSurface()->blitFrom(*_lastFrame);
+
+	if (_getFirstFrame) {
+		_video->stop();
+		_getFirstFrame = false;
+	}
+
+	if (_video->endOfVideo()) {
+		if (_looping) {
+			_video->rewind();
+		} else {
+			_channel->_movieRate = 0.0;
+		}
+	}
+
+	return widget;
+}
+
+uint DigitalVideoCastMember::getDuration() {
+	if (!_video || !_video->isVideoLoaded()) {
+		Common::String path = getCast()->getVideoPath(_castId);
+		if (!path.empty())
+			loadVideo(pathMakeRelative(path));
+
+		_duration = getMovieTotalTime();
+	}
+	return _duration;
+}
+
+uint DigitalVideoCastMember::getMovieCurrentTime() {
+	if (!_video)
+		return 0;
+
+	int stamp = MIN<int>(_video->getTime() * 60 / 1000, getMovieTotalTime());
+
+	return stamp;
+}
+
+uint DigitalVideoCastMember::getMovieTotalTime() {
+	if (!_video)
+		return 0;
+
+	int stamp = _video->getDuration().msecs() * 60 / 1000;
+
+	return stamp;
+}
+
+void DigitalVideoCastMember::seekMovie(int stamp) {
+	if (!_video)
+		return;
+
+	_channel->_startTime = stamp;
+
+	Audio::Timestamp dur = _video->getDuration();
+
+	_video->seek(Audio::Timestamp(_channel->_startTime * 1000 / 60, dur.framerate()));
+}
+
+void DigitalVideoCastMember::setStopTime(int stamp) {
+	if (!_video)
+		return;
+
+	_channel->_stopTime = stamp;
+
+	Audio::Timestamp dur = _video->getDuration();
+
+	_video->setEndTime(Audio::Timestamp(_channel->_stopTime * 1000 / 60, dur.framerate()));
+}
+
+void DigitalVideoCastMember::setMovieRate(double rate) {
+	if (!_video)
+		return;
+
+	_channel->_movieRate = rate;
+
+	if (rate < 0.0)
+		warning("STUB: DigitalVideoCastMember::setMovieRate(%g)", rate);
+	else
+		_video->setRate(Common::Rational((int)(rate * 100.0), 100));
+
+	if (_video->endOfVideo())
+		_video->rewind();
+}
+
+void DigitalVideoCastMember::setFrameRate(int rate) {
+	if (!_video)
+		return;
+
+	warning("STUB: DigitalVideoCastMember::setFrameRate(%d)", rate);
+}
+
+Common::String DigitalVideoCastMember::formatInfo() {
+	return Common::String::format(
+		"initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, filename: \"%s\", duration: %d, enableVideo: %d, enableSound: %d, looping: %d, crop: %d, center: %d, showControls: %d",
+		_initialRect.width(), _initialRect.height(),
+		_initialRect.left, _initialRect.top,
+		_boundingRect.width(), _boundingRect.height(),
+		_boundingRect.left, _boundingRect.top,
+		_filename.c_str(), _duration,
+		_enableVideo, _enableSound,
+		_looping, _crop, _center, _showControls
+	);
+}
+
+bool DigitalVideoCastMember::hasField(int field) {
+	switch (field) {
+	case kTheCenter:
+	case kTheController:
+	case kTheCrop:
+	case kTheDirectToStage:
+	case kTheDuration:
+	case kTheFrameRate:
+	case kTheLoop:
+	case kTheMovieRate:
+	case kTheMovieTime:
+	case kThePausedAtStart:
+	case kThePreLoad:
+	case kTheSound:
+	case kTheVideo:
+	case kTheVolume:
+		return true;
+	default:
+		break;
+	}
+	return CastMember::hasField(field);
+}
+
+Datum DigitalVideoCastMember::getField(int field) {
+	Datum d;
+
+	switch (field) {
+	case kTheCenter:
+		d = _center;
+		break;
+	case kTheController:
+		d = _showControls;
+		break;
+	case kTheCrop:
+		d = _crop;
+		break;
+	case kTheDirectToStage:
+		d = _directToStage;
+		break;
+	case kTheDuration:
+		// sometimes, we will get duration before we start video.
+		// _duration is initialized in startVideo, thus we will not get the correct number.
+		d = (int)getDuration();
+		break;
+	case kTheFrameRate:
+		d = _frameRate;
+		break;
+	case kTheLoop:
+		d = _looping;
+		break;
+	case kThePausedAtStart:
+		d = _pausedAtStart;
+		break;
+	case kThePreLoad:
+		d = _preload;
+		break;
+	case kTheSound:
+		d = _enableSound;
+		break;
+	case kTheVideo:
+		d = _enableVideo;
+		break;
+	default:
+		d = CastMember::getField(field);
+	}
+
+	return d;
+}
+
+bool DigitalVideoCastMember::setField(int field, const Datum &d) {
+	switch (field) {
+	case kTheCenter:
+		_center = (bool)d.asInt();
+		return true;
+	case kTheController:
+		_showControls = (bool)d.asInt();
+		return true;
+	case kTheCrop:
+		_crop = (bool)d.asInt();
+		return true;
+	case kTheDirectToStage:
+		_directToStage = (bool)d.asInt();
+		return true;
+	case kTheDuration:
+		warning("DigitalVideoCastMember::setField(): Attempt to set read-only field %s of cast %d", g_lingo->entity2str(field), _castId);
+		return false;
+	case kTheFrameRate:
+		_frameRate = d.asInt();
+		setFrameRate(d.asInt());
+		return true;
+	case kTheLoop:
+		_looping = (bool)d.asInt();
+		if (_looping && _channel && _channel->_movieRate == 0.0) {
+			setMovieRate(1.0);
+		}
+		return true;
+	case kThePausedAtStart:
+		_pausedAtStart = (bool)d.asInt();
+		return true;
+	case kThePreLoad:
+		_preload = (bool)d.asInt();
+		return true;
+	case kTheSound:
+		_enableSound = (bool)d.asInt();
+		return true;
+	case kTheVideo:
+		_enableVideo = (bool)d.asInt();
+		return true;
+	default:
+		break;
+	}
+
+	return CastMember::setField(field, d);
+}
+
+} // End of namespace Director
diff --git a/engines/director/castmember/digitalvideo.h b/engines/director/castmember/digitalvideo.h
new file mode 100644
index 00000000000..f2647b735d5
--- /dev/null
+++ b/engines/director/castmember/digitalvideo.h
@@ -0,0 +1,87 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef DIRECTOR_CASTMEMBER_DIGITALVIDEO_H
+#define DIRECTOR_CASTMEMBER_DIGITALVIDEO_H
+
+#include "director/castmember/castmember.h"
+
+namespace Video {
+class VideoDecoder;
+}
+
+namespace Director {
+
+class DigitalVideoCastMember : public CastMember {
+public:
+	DigitalVideoCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version);
+	~DigitalVideoCastMember();
+
+	bool isModified() override;
+	Graphics::MacWidget *createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) override;
+
+	bool loadVideo(Common::String path);
+	void startVideo(Channel *channel);
+	void stopVideo();
+	void rewindVideo();
+
+	uint getMovieCurrentTime();
+	uint getDuration();
+	uint getMovieTotalTime();
+	void seekMovie(int stamp);
+	void setStopTime(int stamp);
+	void setMovieRate(double rate);
+	void setFrameRate(int rate);
+
+	bool hasField(int field) override;
+	Datum getField(int field) override;
+	bool setField(int field, const Datum &value) override;
+
+	Common::String formatInfo() override;
+
+	Common::String _filename;
+
+	uint32 _vflags;
+	bool _looping;
+	bool _pausedAtStart;
+	bool _enableVideo;
+	bool _enableSound;
+	bool _crop;
+	bool _center;
+	bool _preload;
+	bool _showControls;
+	bool _directToStage;
+	bool _avimovie, _qtmovie;
+	FrameRateType _frameRateType;
+
+	uint16 _frameRate;
+	bool _getFirstFrame;
+	int _duration;
+
+	Video::VideoDecoder *_video;
+	Graphics::Surface *_lastFrame;
+
+	Channel *_channel;
+};
+
+} // End of namespace Director
+
+#endif
diff --git a/engines/director/castmember/filmloop.cpp b/engines/director/castmember/filmloop.cpp
new file mode 100644
index 00000000000..5a654eebda9
--- /dev/null
+++ b/engines/director/castmember/filmloop.cpp
@@ -0,0 +1,392 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "graphics/surface.h"
+#include "graphics/macgui/macwidget.h"
+
+#include "video/avi_decoder.h"
+#include "video/qt_decoder.h"
+
+#include "director/director.h"
+#include "director/cast.h"
+#include "director/channel.h"
+#include "director/movie.h"
+#include "director/window.h"
+#include "director/castmember/filmloop.h"
+
+namespace Director {
+
+FilmLoopCastMember::FilmLoopCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
+		: CastMember(cast, castId, stream) {
+	_type = kCastFilmLoop;
+	_looping = true;
+	_enableSound = true;
+	_crop = false;
+	_center = false;
+}
+
+FilmLoopCastMember::~FilmLoopCastMember() {
+
+}
+
+bool FilmLoopCastMember::isModified() {
+	if (_frames.size())
+		return true;
+
+	if (_initialRect.width() && _initialRect.height())
+		return true;
+
+	return false;
+}
+
+Common::Array<Channel> *FilmLoopCastMember::getSubChannels(Common::Rect &bbox, Channel *channel) {
+	Common::Rect widgetRect(bbox.width() ? bbox.width() : _initialRect.width(), bbox.height() ? bbox.height() : _initialRect.height());
+
+	_subchannels.clear();
+
+	if (channel->_filmLoopFrame >= _frames.size()) {
+		warning("Film loop frame %d requested, only %d available", channel->_filmLoopFrame, _frames.size());
+		return &_subchannels;
+	}
+
+	// get the list of sprite IDs for this frame
+	Common::Array<int> spriteIds;
+	for (Common::HashMap<int, Director::Sprite>::iterator iter = _frames[channel->_filmLoopFrame].sprites.begin(); iter != _frames[channel->_filmLoopFrame].sprites.end(); ++iter) {
+		spriteIds.push_back(iter->_key);
+	}
+	Common::sort(spriteIds.begin(), spriteIds.end());
+
+	// copy the sprites in order to the list
+	for (Common::Array<int>::iterator iter = spriteIds.begin(); iter != spriteIds.end(); ++iter) {
+		Sprite src = _frames[channel->_filmLoopFrame].sprites[*iter];
+		if (!src._cast)
+			continue;
+		// translate sprite relative to the global bounding box
+		int16 relX = (src._startPoint.x - _initialRect.left) * widgetRect.width() / _initialRect.width();
+		int16 relY = (src._startPoint.y - _initialRect.top) * widgetRect.height() / _initialRect.height();
+		int16 absX = relX + bbox.left;
+		int16 absY = relY + bbox.top;
+		int16 width = src._width * widgetRect.width() / _initialRect.width();
+		int16 height = src._height * widgetRect.height() / _initialRect.height();
+
+		Channel chan(&src);
+		chan._currentPoint = Common::Point(absX, absY);
+		chan._width = width;
+		chan._height = height;
+
+		_subchannels.push_back(chan);
+
+	}
+	// Initialise the widgets on all of the subchannels.
+	// This has to be done once the list has been constructed, otherwise
+	// the list grow operation will erase the widgets as they aren't
+	// part of the Channel assignment constructor.
+	for (auto &iter : _subchannels) {
+		iter.replaceWidget();
+	}
+
+	return &_subchannels;
+}
+
+void FilmLoopCastMember::loadFilmLoopData(Common::SeekableReadStreamEndian &stream) {
+	_initialRect = Common::Rect();
+	_frames.clear();
+
+	uint32 size = stream.readUint32BE();
+	if (debugChannelSet(5, kDebugLoading)) {
+		debugC(5, kDebugLoading, "SCVW body:");
+		uint32 pos = stream.pos();
+		stream.seek(0);
+		stream.hexdump(size);
+		stream.seek(pos);
+	}
+	uint16 channelSize = 16;
+	FilmLoopFrame newFrame;
+
+	while (stream.pos() < size) {
+		uint16 frameSize = stream.readUint16BE() - 2;
+		if (debugChannelSet(5, kDebugLoading)) {
+			debugC(5, kDebugLoading, "Frame entry:");
+			stream.hexdump(frameSize);
+		}
+
+		while (frameSize > 0) {
+			int msgWidth = stream.readByte() * 2;
+			int order = stream.readByte() * 2 - 0x20;
+			frameSize -= 2;
+			debugC(8, kDebugLoading, "Message: msgWidth %d, order %d", msgWidth, order);
+			if (debugChannelSet(8, kDebugLoading)) {
+				stream.hexdump(msgWidth);
+			}
+
+			int fieldPosition = order;
+			int finishPosition = order + msgWidth;
+			while (fieldPosition < finishPosition) {
+				int channel = (fieldPosition / channelSize);
+				int channelOffset = fieldPosition % channelSize;
+
+				Sprite sprite(nullptr);
+				sprite._movie = g_director->getCurrentMovie();
+				if (newFrame.sprites.contains(channel)) {
+					sprite = newFrame.sprites.getVal(channel);
+				}
+				sprite._spriteType = kCastMemberSprite;
+				sprite._puppet = 1;
+				sprite._stretch = 1;
+
+				switch (channelOffset) {
+				case kSpritePositionUnk1:
+					stream.readByte();
+					fieldPosition++;
+					break;
+				case kSpritePositionEnabled:
+					sprite._enabled = stream.readByte() != 0;
+					fieldPosition++;
+					break;
+				case kSpritePositionUnk2:
+					stream.readUint16BE();
+					fieldPosition += 2;
+					break;
+				case kSpritePositionFlags:
+					sprite._thickness = stream.readByte();
+					sprite._inkData = stream.readByte();
+					sprite._ink = static_cast<InkType>(sprite._inkData & 0x3f);
+
+					if (sprite._inkData & 0x40)
+						sprite._trails = 1;
+					else
+						sprite._trails = 0;
+
+					fieldPosition += 2;
+					break;
+				case kSpritePositionCastId:
+					sprite.setCast(CastMemberID(stream.readUint16(), 0));
+					fieldPosition += 2;
+					break;
+				case kSpritePositionY:
+					sprite._startPoint.y = stream.readUint16();
+					fieldPosition += 2;
+					break;
+				case kSpritePositionX:
+					sprite._startPoint.x = stream.readUint16();
+					fieldPosition += 2;
+					break;
+				case kSpritePositionWidth:
+					sprite._width = stream.readUint16();
+					fieldPosition += 2;
+					break;
+				case kSpritePositionHeight:
+					sprite._height = stream.readUint16();
+					fieldPosition += 2;
+					break;
+				default:
+					stream.readUint16BE();
+					fieldPosition += 2;
+					break;
+				}
+				newFrame.sprites.setVal(channel, sprite);
+			}
+
+			frameSize -= msgWidth;
+		}
+
+		for (Common::HashMap<int, Sprite>::iterator s = newFrame.sprites.begin(); s != newFrame.sprites.end(); ++s) {
+			debugC(5, kDebugLoading, "Sprite: channel %d, castId %s, bbox %d %d %d %d", s->_key,
+					s->_value._castId.asString().c_str(), s->_value._startPoint.x, s->_value._startPoint.y,
+					s->_value._width, s->_value._height);
+
+			Common::Point topLeft = s->_value._startPoint + s->_value.getRegistrationOffset();
+			Common::Rect spriteBbox(
+				topLeft.x,
+				topLeft.y,
+				topLeft.x + s->_value._width,
+				topLeft.y + s->_value._height
+			);
+			if (!((spriteBbox.width() == 0) && (spriteBbox.height() == 0))) {
+				if ((_initialRect.width() == 0) && (_initialRect.height() == 0)) {
+					_initialRect = spriteBbox;
+				} else {
+					_initialRect.extend(spriteBbox);
+				}
+			}
+			debugC(8, kDebugLoading, "New bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
+
+		}
+
+		_frames.push_back(newFrame);
+
+	}
+	debugC(5, kDebugLoading, "Full bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
+}
+
+void FilmLoopCastMember::loadFilmLoopDataV4(Common::SeekableReadStreamEndian &stream) {
+	_initialRect = Common::Rect();
+	_frames.clear();
+
+	uint32 size = stream.readUint32BE();
+	if (debugChannelSet(5, kDebugLoading)) {
+		debugC(5, kDebugLoading, "SCVW body:");
+		uint32 pos = stream.pos();
+		stream.seek(0);
+		stream.hexdump(size);
+		stream.seek(pos);
+	}
+	uint32 framesOffset = stream.readUint32BE();
+	if (debugChannelSet(5, kDebugLoading)) {
+		debugC(5, kDebugLoading, "SCVW header:");
+		stream.hexdump(framesOffset - 8);
+	}
+	stream.skip(6);
+	uint16 channelSize = stream.readUint16BE(); // should be 20!
+	stream.skip(framesOffset - 16);
+
+	FilmLoopFrame newFrame;
+
+	while (stream.pos() < size) {
+		uint16 frameSize = stream.readUint16BE() - 2;
+		if (debugChannelSet(5, kDebugLoading)) {
+			debugC(5, kDebugLoading, "Frame entry:");
+			stream.hexdump(frameSize);
+		}
+
+		while (frameSize > 0) {
+			uint16 msgWidth = stream.readUint16BE();
+			uint16 order = stream.readUint16BE();
+			frameSize -= 4;
+
+			int channel = (order / channelSize) - 1;
+			int channelOffset = order % channelSize;
+
+			Sprite sprite(nullptr);
+			sprite._movie = g_director->getCurrentMovie();
+			if (newFrame.sprites.contains(channel)) {
+				sprite = newFrame.sprites.getVal(channel);
+			}
+			debugC(8, kDebugLoading, "Message: msgWidth %d, channel %d, channelOffset %d", msgWidth, channel, channelOffset);
+			if (debugChannelSet(8, kDebugLoading)) {
+				stream.hexdump(msgWidth);
+			}
+			sprite._puppet = 1;
+			sprite._stretch = 1;
+
+			int fieldPosition = channelOffset;
+			int finishPosition = channelOffset + msgWidth;
+			while (fieldPosition < finishPosition) {
+				switch (fieldPosition) {
+				case kSpritePositionUnk1:
+					stream.readByte();
+					fieldPosition++;
+					break;
+				case kSpritePositionEnabled:
+					sprite._enabled = stream.readByte() != 0;
+					fieldPosition++;
+					break;
+				case kSpritePositionUnk2:
+					stream.readUint16BE();
+					fieldPosition += 2;
+					break;
+				case kSpritePositionFlags:
+					sprite._thickness = stream.readByte();
+					sprite._inkData = stream.readByte();
+					sprite._ink = static_cast<InkType>(sprite._inkData & 0x3f);
+
+					if (sprite._inkData & 0x40)
+						sprite._trails = 1;
+					else
+						sprite._trails = 0;
+
+					fieldPosition += 2;
+					break;
+				case kSpritePositionCastId:
+					sprite.setCast(CastMemberID(stream.readUint16(), 0));
+					fieldPosition += 2;
+					break;
+				case kSpritePositionY:
+					sprite._startPoint.y = stream.readUint16();
+					fieldPosition += 2;
+					break;
+				case kSpritePositionX:
+					sprite._startPoint.x = stream.readUint16();
+					fieldPosition += 2;
+					break;
+				case kSpritePositionWidth:
+					sprite._width = stream.readUint16();
+					fieldPosition += 2;
+					break;
+				case kSpritePositionHeight:
+					sprite._height = stream.readUint16();
+					fieldPosition += 2;
+					break;
+				default:
+					stream.readUint16BE();
+					fieldPosition += 2;
+					break;
+				}
+			}
+
+			frameSize -= msgWidth;
+
+			newFrame.sprites.setVal(channel, sprite);
+		}
+
+		for (Common::HashMap<int, Sprite>::iterator s = newFrame.sprites.begin(); s != newFrame.sprites.end(); ++s) {
+			debugC(5, kDebugLoading, "Sprite: channel %d, castId %s, bbox %d %d %d %d", s->_key,
+					s->_value._castId.asString().c_str(), s->_value._startPoint.x, s->_value._startPoint.y,
+					s->_value._width, s->_value._height);
+
+			Common::Point topLeft = s->_value._startPoint + s->_value.getRegistrationOffset();
+			Common::Rect spriteBbox(
+				topLeft.x,
+				topLeft.y,
+				topLeft.x + s->_value._width,
+				topLeft.y + s->_value._height
+			);
+			if (!((spriteBbox.width() == 0) && (spriteBbox.height() == 0))) {
+				if ((_initialRect.width() == 0) && (_initialRect.height() == 0)) {
+					_initialRect = spriteBbox;
+				} else {
+					_initialRect.extend(spriteBbox);
+				}
+			}
+			debugC(8, kDebugLoading, "New bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
+
+		}
+
+		_frames.push_back(newFrame);
+
+	}
+	debugC(5, kDebugLoading, "Full bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
+
+}
+
+Common::String FilmLoopCastMember::formatInfo() {
+	return Common::String::format(
+		"initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, frameCount: %d, subchannelCount: %d, enableSound: %d, looping: %d, crop: %d, center: %d",
+		_initialRect.width(), _initialRect.height(),
+		_initialRect.left, _initialRect.top,
+		_boundingRect.width(), _boundingRect.height(),
+		_boundingRect.left, _boundingRect.top,
+		_frames.size(), _subchannels.size(), _enableSound, _looping,
+		_crop, _center
+	);
+}
+
+} // End of namespace Director
diff --git a/engines/director/castmember/filmloop.h b/engines/director/castmember/filmloop.h
new file mode 100644
index 00000000000..297eb2b2775
--- /dev/null
+++ b/engines/director/castmember/filmloop.h
@@ -0,0 +1,61 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef DIRECTOR_CASTMEMBER_FILMLOOP_H
+#define DIRECTOR_CASTMEMBER_FILMLOOP_H
+
+#include "director/castmember/castmember.h"
+
+namespace Director {
+
+class Sprite;
+
+struct FilmLoopFrame {
+	Common::HashMap<int, Sprite> sprites;
+};
+
+class FilmLoopCastMember : public CastMember {
+public:
+	FilmLoopCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version);
+	~FilmLoopCastMember();
+
+	bool isModified() override;
+	//Graphics::MacWidget *createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) override;
+
+	Common::Array<Channel> *getSubChannels(Common::Rect &bbox, Channel *channel);
+
+	void loadFilmLoopData(Common::SeekableReadStreamEndian &stream);
+	void loadFilmLoopDataV4(Common::SeekableReadStreamEndian &stream);
+
+	Common::String formatInfo() override;
+
+	bool _enableSound;
+	bool _looping;
+	bool _crop;
+	bool _center;
+
+	Common::Array<FilmLoopFrame> _frames;
+	Common::Array<Channel> _subchannels;
+};
+
+} // End of namespace Director
+
+#endif
diff --git a/engines/director/castmember/movie.cpp b/engines/director/castmember/movie.cpp
new file mode 100644
index 00000000000..38026310f8d
--- /dev/null
+++ b/engines/director/castmember/movie.cpp
@@ -0,0 +1,61 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "director/director.h"
+#include "director/movie.h"
+#include "director/castmember/movie.h"
+
+namespace Director {
+
+MovieCastMember::MovieCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
+		: CastMember(cast, castId, stream) {
+	_type = kCastMovie;
+
+	_initialRect = Movie::readRect(stream);
+	_flags = stream.readUint32();
+
+	_looping = !(_flags & 0x20);
+	_enableScripts = _flags & 0x10;
+	_enableSound = _flags & 0x08;
+	_crop = !(_flags & 0x02);
+	_center = _flags & 0x01;
+
+	if (debugChannelSet(2, kDebugLoading))
+		_initialRect.debugPrint(2, "MovieCastMember(): rect:");
+	debugC(2, kDebugLoading, "MovieCastMember(): flags: (%d 0x%04x)", _flags, _flags);
+	debugC(2, kDebugLoading, "_looping: %d, _enableScripts %d, _enableSound: %d, _crop %d, _center: %d",
+			_looping, _enableScripts, _enableSound, _crop, _center);
+
+}
+
+Common::String MovieCastMember::formatInfo() {
+	return Common::String::format(
+		"initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, enableScripts: %d, enableSound: %d, looping: %d, crop: %d, center: %d",
+		_initialRect.width(), _initialRect.height(),
+		_initialRect.left, _initialRect.top,
+		_boundingRect.width(), _boundingRect.height(),
+		_boundingRect.left, _boundingRect.top,
+		_enableScripts, _enableSound, _looping,
+		_crop, _center
+	);
+}
+
+} // End of namespace Director
diff --git a/engines/director/castmember/movie.h b/engines/director/castmember/movie.h
new file mode 100644
index 00000000000..31d3322f1a9
--- /dev/null
+++ b/engines/director/castmember/movie.h
@@ -0,0 +1,45 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef DIRECTOR_CASTMEMBER_MOVIE_H
+#define DIRECTOR_CASTMEMBER_MOVIE_H
+
+#include "director/castmember/castmember.h"
+
+namespace Director {
+
+class MovieCastMember : public CastMember {
+public:
+	MovieCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version);
+
+	Common::String formatInfo() override;
+
+	uint32 _flags;
+	bool _looping;
+	bool _enableScripts;
+	bool _enableSound;
+	bool _crop;
+	bool _center;
+};
+
+} // End of namespace Director
+
+#endif
diff --git a/engines/director/castmember/palette.cpp b/engines/director/castmember/palette.cpp
new file mode 100644
index 00000000000..d15a90f84f3
--- /dev/null
+++ b/engines/director/castmember/palette.cpp
@@ -0,0 +1,44 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "director/director.h"
+#include "director/castmember/palette.h"
+
+namespace Director {
+
+PaletteCastMember::PaletteCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
+	: CastMember(cast, castId, stream) {
+	_type = kCastPalette;
+	_palette = nullptr;
+}
+
+Common::String PaletteCastMember::formatInfo() {
+	Common::String result;
+	if (_palette) {
+		result = "data: ";
+		for (size_t i = 0; i < (size_t)_palette->length; i++) {
+			result += Common::String::format("%02X%02X%02X", _palette->palette[3 * i], _palette->palette[3 * i + 1], _palette->palette[3 * i + 2]);
+		}
+	}
+	return result;
+}
+
+}
diff --git a/engines/director/castmember/palette.h b/engines/director/castmember/palette.h
new file mode 100644
index 00000000000..769bf9817db
--- /dev/null
+++ b/engines/director/castmember/palette.h
@@ -0,0 +1,42 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef DIRECTOR_CASTMEMBER_PALETTE_H
+#define DIRECTOR_CASTMEMBER_PALETTE_H
+
+#include "director/castmember/castmember.h"
+
+namespace Director {
+
+class PaletteCastMember : public CastMember {
+public:
+	PaletteCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version);
+	int getPaletteId() { return _palette ? _palette->id : 0; }
+	void activatePalette() { if (_palette) g_director->setPalette(_palette->id); }
+
+	Common::String formatInfo() override;
+
+	PaletteV4 *_palette;
+};
+
+} // End of namespace Director
+
+#endif
diff --git a/engines/director/castmember/script.cpp b/engines/director/castmember/script.cpp
new file mode 100644
index 00000000000..f9250597efd
--- /dev/null
+++ b/engines/director/castmember/script.cpp
@@ -0,0 +1,61 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "director/director.h"
+#include "director/castmember/script.h"
+
+namespace Director {
+
+ScriptCastMember::ScriptCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
+		: CastMember(cast, castId, stream) {
+	_type = kCastLingoScript;
+	_scriptType = kNoneScript;
+
+	if (debugChannelSet(5, kDebugLoading)) {
+		debugC(5, kDebugLoading, "ScriptCastMember::ScriptCastMember(): Contents");
+		stream.hexdump(stream.size());
+	}
+
+	if (version < kFileVer400) {
+		error("Unhandled Script cast");
+	} else if (version >= kFileVer400 && version < kFileVer600) {
+		byte unk1 = stream.readByte();
+		byte type = stream.readByte();
+
+		switch (type) {
+		case 1:
+			_scriptType = kScoreScript;
+			break;
+		case 3:
+			_scriptType = kMovieScript;
+			break;
+		default:
+			error("ScriptCastMember: Unprocessed script type: %d", type);
+		}
+
+		debugC(3, kDebugLoading, "CASt: Script type: %s (%d), unk1: %d", scriptType2str(_scriptType), type, unk1);
+
+		stream.readByte(); // There should be no more data
+		assert(stream.eos());
+	}
+}
+
+}
diff --git a/engines/director/castmember/script.h b/engines/director/castmember/script.h
new file mode 100644
index 00000000000..a6c1e8610a4
--- /dev/null
+++ b/engines/director/castmember/script.h
@@ -0,0 +1,38 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef DIRECTOR_CASTMEMBER_SCRIPT_H
+#define DIRECTOR_CASTMEMBER_SCRIPT_H
+
+#include "director/castmember/castmember.h"
+
+namespace Director {
+
+class ScriptCastMember : public CastMember {
+public:
+	ScriptCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version);
+
+	ScriptType _scriptType;
+};
+
+} // End of namespace Director
+
+#endif
diff --git a/engines/director/castmember/shape.cpp b/engines/director/castmember/shape.cpp
new file mode 100644
index 00000000000..4036db4191d
--- /dev/null
+++ b/engines/director/castmember/shape.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 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "director/director.h"
+#include "director/movie.h"
+#include "director/castmember/shape.h"
+
+namespace Director {
+
+ShapeCastMember::ShapeCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
+		: CastMember(cast, castId, stream) {
+	_type = kCastShape;
+
+	byte unk1;
+
+	_ink = kInkTypeCopy;
+
+	if (version < kFileVer400) {
+		unk1 = stream.readByte();
+		_shapeType = static_cast<ShapeType>(stream.readByte());
+		_initialRect = Movie::readRect(stream);
+		_pattern = stream.readUint16BE();
+		// Normalize D2 and D3 colors from -128 ... 127 to 0 ... 255.
+		_fgCol = g_director->transformColor((128 + stream.readByte()) & 0xff);
+		_bgCol = g_director->transformColor((128 + stream.readByte()) & 0xff);
+		_fillType = stream.readByte();
+		_ink = static_cast<InkType>(_fillType & 0x3f);
+		_lineThickness = stream.readByte();
+		_lineDirection = stream.readByte();
+	} else if (version >= kFileVer400 && version < kFileVer500) {
+		unk1 = stream.readByte();
+		_shapeType = static_cast<ShapeType>(stream.readByte());
+		_initialRect = Movie::readRect(stream);
+		_pattern = stream.readUint16BE();
+		_fgCol = g_director->transformColor((uint8)stream.readByte());
+		_bgCol = g_director->transformColor((uint8)stream.readByte());
+		_fillType = stream.readByte();
+		_ink = static_cast<InkType>(_fillType & 0x3f);
+		_lineThickness = stream.readByte();
+		_lineDirection = stream.readByte();
+	} else {
+		stream.readByte(); // FIXME: Was this copied from D4 by mistake?
+		unk1 = stream.readByte();
+
+		_initialRect = Movie::readRect(stream);
+		_boundingRect = Movie::readRect(stream);
+
+		_shapeType = kShapeRectangle;
+		_pattern = 0;
+		_fgCol = _bgCol = 0;
+		_fillType = 0;
+		_lineThickness = 1;
+		_lineDirection = 0;
+	}
+	_modified = false;
+
+	debugC(3, kDebugLoading, "ShapeCastMember: unk1: %x type: %d pat: %d fg: %d bg: %d fill: %d thick: %d dir: %d",
+		unk1, _shapeType, _pattern, _fgCol, _bgCol, _fillType, _lineThickness, _lineDirection);
+
+	if (debugChannelSet(3, kDebugLoading))
+		_initialRect.debugPrint(0, "ShapeCastMember: rect:");
+}
+
+void ShapeCastMember::setBackColor(uint32 bgCol) {
+	_bgCol = bgCol;
+	_modified = true;
+}
+
+void ShapeCastMember::setForeColor(uint32 fgCol) {
+	_fgCol = fgCol;
+	_modified = true;
+}
+
+Common::String ShapeCastMember::formatInfo() {
+	return Common::String::format(
+		"initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, foreColor: %d, backColor: %d, shapeType: %d, pattern: %d, fillType: %d, lineThickness: %d, lineDirection: %d, ink: %d",
+		_initialRect.width(), _initialRect.height(),
+		_initialRect.left, _initialRect.top,
+		_boundingRect.width(), _boundingRect.height(),
+		_boundingRect.left, _boundingRect.top,
+		getForeColor(), getBackColor(),
+		_shapeType, _pattern, _fillType,
+		_lineThickness, _lineDirection, _ink
+	);
+}
+
+}
diff --git a/engines/director/castmember/shape.h b/engines/director/castmember/shape.h
new file mode 100644
index 00000000000..2f1eb3d9efd
--- /dev/null
+++ b/engines/director/castmember/shape.h
@@ -0,0 +1,53 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef DIRECTOR_CASTMEMBER_SHAPE_H
+#define DIRECTOR_CASTMEMBER_SHAPE_H
+
+#include "director/castmember/castmember.h"
+
+namespace Director {
+
+class ShapeCastMember : public CastMember {
+public:
+	ShapeCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version);
+	uint32 getForeColor() override { return _fgCol; }
+	uint32 getBackColor() override { return _bgCol; }
+	void setBackColor(uint32 bgCol) override;
+	void setForeColor(uint32 fgCol) override;
+
+	Common::String formatInfo() override;
+
+	ShapeType _shapeType;
+	uint16 _pattern;
+	byte _fillType;
+	byte _lineThickness;
+	byte _lineDirection;
+	InkType _ink;
+
+private:
+	uint32 _fgCol;
+	uint32 _bgCol;
+};
+
+} // End of namespace Director
+
+#endif
diff --git a/engines/director/castmember/sound.cpp b/engines/director/castmember/sound.cpp
new file mode 100644
index 00000000000..4d62b1be855
--- /dev/null
+++ b/engines/director/castmember/sound.cpp
@@ -0,0 +1,46 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "director/director.h"
+#include "director/sound.h"
+#include "director/castmember/sound.h"
+
+namespace Director {
+
+SoundCastMember::SoundCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
+		: CastMember(cast, castId, stream) {
+	_type = kCastSound;
+	_audio = nullptr;
+	_looping = 0;
+}
+
+SoundCastMember::~SoundCastMember() {
+	if (_audio)
+		delete _audio;
+}
+
+Common::String SoundCastMember::formatInfo() {
+	return Common::String::format(
+		"looping: %d", _looping
+	);
+}
+
+} // End of namespace Director
diff --git a/engines/director/castmember/sound.h b/engines/director/castmember/sound.h
new file mode 100644
index 00000000000..57e9372fef7
--- /dev/null
+++ b/engines/director/castmember/sound.h
@@ -0,0 +1,44 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef DIRECTOR_CASTMEMBER_SOUND_H
+#define DIRECTOR_CASTMEMBER_SOUND_H
+
+#include "director/castmember/castmember.h"
+
+namespace Director {
+
+class AudioDecoder;
+
+class SoundCastMember : public CastMember {
+public:
+	SoundCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version);
+	~SoundCastMember();
+
+	Common::String formatInfo() override;
+
+	bool _looping;
+	AudioDecoder *_audio;
+};
+
+} // End of namespace Director
+
+#endif
diff --git a/engines/director/castmember/text.cpp b/engines/director/castmember/text.cpp
new file mode 100644
index 00000000000..1f26b8b6543
--- /dev/null
+++ b/engines/director/castmember/text.cpp
@@ -0,0 +1,652 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "graphics/macgui/macbutton.h"
+#include "graphics/macgui/mactext.h"
+
+#include "director/director.h"
+#include "director/channel.h"
+#include "director/movie.h"
+#include "director/score.h"
+#include "director/window.h"
+#include "director/castmember/text.h"
+#include "director/lingo/lingo.h"
+#include "director/lingo/lingo-the.h"
+
+namespace Director {
+
+TextCastMember::TextCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version, uint8 flags1, bool asButton)
+		: CastMember(cast, castId, stream) {
+	_type = kCastText;
+
+	_borderSize = kSizeNone;
+	_gutterSize = kSizeNone;
+	_boxShadow = kSizeNone;
+	_buttonType = kTypeButton;
+	_editable = false;
+	_maxHeight = _textHeight = 0;
+
+	_bgcolor = 0;
+	_fgcolor = 0xff;
+
+	_textFlags = 0;
+	_scroll = 0;
+	_fontId = 1;
+	_fontSize = 12;
+	_textType = kTextTypeFixed;
+	_textAlign = kTextAlignLeft;
+	_textShadow = kSizeNone;
+	_textSlant = 0;
+	_bgpalinfo1 = _bgpalinfo2 = _bgpalinfo3 = 0;
+	_fgpalinfo1 = _fgpalinfo2 = _fgpalinfo3 = 0xff;
+
+	// seems like the line spacing is default to 1 in D4
+	_lineSpacing = g_director->getVersion() >= 400 ? 1 : 0;
+
+	if (version < kFileVer400) {
+		_flags1 = flags1; // region: 0 - auto, 1 - matte, 2 - disabled
+		_borderSize = static_cast<SizeType>(stream.readByte());
+		_gutterSize = static_cast<SizeType>(stream.readByte());
+		_boxShadow = static_cast<SizeType>(stream.readByte());
+		_textType = static_cast<TextType>(stream.readByte());
+		_textAlign = static_cast<TextAlignType>(stream.readUint16());
+		_bgpalinfo1 = stream.readUint16();
+		_bgpalinfo2 = stream.readUint16();
+		_bgpalinfo3 = stream.readUint16();
+
+		uint32 pad2;
+		uint16 pad3;
+		uint16 pad4 = 0;
+		uint16 totalTextHeight;
+
+		if (version < kFileVer300) {
+			pad2 = stream.readUint16();
+			if (pad2 != 0) { // In D2 there are values
+				warning("TextCastMember: pad2: %x", pad2);
+			}
+
+			_initialRect = Movie::readRect(stream);
+			pad3 = stream.readUint16();
+
+			_textShadow = static_cast<SizeType>(stream.readByte());
+			_textFlags = stream.readByte();
+			if (_textFlags & 0xf8)
+				warning("Unprocessed text cast flags: %x", _textFlags & 0xf8);
+
+			totalTextHeight = stream.readUint16();
+		} else {
+			pad2 = stream.readUint16();
+			_initialRect = Movie::readRect(stream);
+			pad3 = stream.readUint16();
+			_textFlags = stream.readUint16(); // 1: editable, 2: auto tab, 4: don't wrap
+			_editable = _textFlags & 0x1;
+			totalTextHeight = stream.readUint16();
+		}
+
+		debugC(2, kDebugLoading, "TextCastMember(): flags1: %d, border: %d gutter: %d shadow: %d textType: %d align: %04x",
+				_flags1, _borderSize, _gutterSize, _boxShadow, _textType, _textAlign);
+		debugC(2, kDebugLoading, "TextCastMember(): background rgb: 0x%04x 0x%04x 0x%04x, pad2: %x pad3: %d pad4: %d shadow: %d flags: %d totHeight: %d",
+				_bgpalinfo1, _bgpalinfo2, _bgpalinfo3, pad2, pad3, pad4, _textShadow, _textFlags, totalTextHeight);
+		if (debugChannelSet(2, kDebugLoading)) {
+			_initialRect.debugPrint(2, "TextCastMember(): rect:");
+		}
+	} else if (version >= kFileVer400 && version < kFileVer500) {
+		_flags1 = flags1;
+		_borderSize = static_cast<SizeType>(stream.readByte());
+		_gutterSize = static_cast<SizeType>(stream.readByte());
+		_boxShadow = static_cast<SizeType>(stream.readByte());
+		_textType = static_cast<TextType>(stream.readByte());
+		_textAlign = static_cast<TextAlignType>(stream.readSint16()); // this is because 'right' is -1? or should that be 255?
+		_bgpalinfo1 = stream.readUint16();
+		_bgpalinfo2 = stream.readUint16();
+		_bgpalinfo3 = stream.readUint16();
+		_scroll = stream.readUint16();
+
+		_fontId = 1; // this is in STXT
+
+		_initialRect = Movie::readRect(stream);
+		_maxHeight = stream.readUint16();
+		_textShadow = static_cast<SizeType>(stream.readByte());
+		_textFlags = stream.readByte(); // 1: editable, 2: auto tab 4: don't wrap
+		_editable = _textFlags & 0x1;
+
+		_textHeight = stream.readUint16();
+		_textSlant = 0;
+		debugC(2, kDebugLoading, "TextCastMember(): flags1: %d, border: %d gutter: %d shadow: %d textType: %d align: %04x",
+				_flags1, _borderSize, _gutterSize, _boxShadow, _textType, _textAlign);
+		debugC(2, kDebugLoading, "TextCastMember(): background rgb: 0x%04x 0x%04x 0x%04x, shadow: %d flags: %d textHeight: %d",
+				_bgpalinfo1, _bgpalinfo2, _bgpalinfo3, _textShadow, _textFlags, _textHeight);
+		if (debugChannelSet(2, kDebugLoading)) {
+			_initialRect.debugPrint(2, "TextCastMember(): rect:");
+		}
+	} else {
+		_fontId = 1;
+
+		stream.readUint32();
+		stream.readUint32();
+		stream.readUint32();
+		stream.readUint32();
+		uint16 skip = stream.readUint16();
+		for (int i = 0; i < skip; i++)
+			stream.readUint32();
+
+		stream.readUint32();
+		stream.readUint32();
+		stream.readUint32();
+		stream.readUint32();
+		stream.readUint32();
+		stream.readUint32();
+
+		_initialRect = Movie::readRect(stream);
+		_boundingRect = Movie::readRect(stream);
+
+		stream.readUint32();
+		stream.readUint16();
+		stream.readUint16();
+	}
+
+	if (asButton) {
+		_type = kCastButton;
+
+		if (version < kFileVer500) {
+			_buttonType = static_cast<ButtonType>(stream.readUint16BE() - 1);
+		} else {
+			warning("TextCastMember(): Attempting to initialize >D4 button castmember");
+			_buttonType = kTypeButton;
+		}
+	}
+
+	_bgcolor = g_director->_wm->findBestColor(_bgpalinfo1 & 0xff, _bgpalinfo2 & 0xff, _bgpalinfo3 & 0xff);
+
+	_modified = true;
+}
+
+void TextCastMember::setColors(uint32 *fgcolor, uint32 *bgcolor) {
+	if (fgcolor)
+		_fgcolor = *fgcolor;
+
+	if (bgcolor)
+		_bgcolor = *bgcolor;
+
+	// if we want to keep the format unchanged, then we need to modify _ftext as well
+	if (_widget)
+		((Graphics::MacText *)_widget)->setColors(_fgcolor, _bgcolor);
+	else
+		_modified = true;
+}
+
+Graphics::TextAlign TextCastMember::getAlignment() {
+	switch (_textAlign) {
+	case kTextAlignRight:
+		return Graphics::kTextAlignRight;
+	case kTextAlignCenter:
+		return Graphics::kTextAlignCenter;
+	case kTextAlignLeft:
+	default:
+		return Graphics::kTextAlignLeft;
+	}
+}
+
+void TextCastMember::setBackColor(uint32 bgCol) {
+	_bgcolor = bgCol;
+	_modified = true;
+}
+
+void TextCastMember::setForeColor(uint32 fgCol) {
+	_fgcolor = fgCol;
+	_modified = true;
+}
+
+void TextCastMember::importStxt(const Stxt *stxt) {
+	_fontId = stxt->_style.fontId;
+	_textSlant = stxt->_style.textSlant;
+	_fontSize = stxt->_style.fontSize;
+	_fgpalinfo1 = stxt->_style.r;
+	_fgpalinfo2 = stxt->_style.g;
+	_fgpalinfo3 = stxt->_style.b;
+	_ftext = stxt->_ftext;
+	_ptext = stxt->_ptext;
+	_rtext = stxt->_rtext;
+
+	// Rectifying _fontId in case of a fallback font
+	Graphics::MacFont macFont(_fontId, _fontSize, _textSlant);
+	g_director->_wm->_fontMan->getFont(&macFont);
+	_fontId = macFont.getId();
+}
+
+Graphics::MacWidget *TextCastMember::createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) {
+	Graphics::MacFont *macFont = new Graphics::MacFont(_fontId, _fontSize, _textSlant);
+	Graphics::MacWidget *widget = nullptr;
+	Common::Rect dims(bbox);
+
+	CastType type = _type;
+	ButtonType buttonType = _buttonType;
+
+	// WORKAROUND: In D2/D3 there can be text casts that have button
+	// information set in the sprite.
+	if (type == kCastText && isButtonSprite(spriteType)) {
+		type = kCastButton;
+		buttonType = ButtonType(spriteType - 8);
+	}
+
+	switch (type) {
+	case kCastText:
+		// for mactext, we can expand now, but we can't shrink. so we may pass the small size when we have adjustToFit text style
+		if (_textType == kTextTypeAdjustToFit) {
+			dims.right = MIN<int>(dims.right, dims.left + _initialRect.width());
+			dims.bottom = MIN<int>(dims.bottom, dims.top + _initialRect.height());
+		} else if (_textType == kTextTypeFixed){
+			// use initialRect to create widget for fixed style text, this maybe related to version.
+			dims.right = MAX<int>(dims.right, dims.left + _initialRect.width());
+			dims.bottom = MAX<int>(dims.bottom, dims.top + _initialRect.height());
+		}
+		widget = new Graphics::MacText(g_director->getCurrentWindow(), bbox.left, bbox.top, dims.width(), dims.height(), g_director->_wm, _ftext, macFont, getForeColor(), getBackColor(), _initialRect.width(), getAlignment(), _lineSpacing, _borderSize, _gutterSize, _boxShadow, _textShadow, _textType == kTextTypeFixed);
+		((Graphics::MacText *)widget)->setSelRange(g_director->getCurrentMovie()->_selStart, g_director->getCurrentMovie()->_selEnd);
+		((Graphics::MacText *)widget)->setEditable(channel->_sprite->_editable);
+		((Graphics::MacText *)widget)->draw();
+
+		// since we disable the ability of setActive in setEdtiable, then we need to set active widget manually
+		if (channel->_sprite->_editable) {
+			Graphics::MacWidget *activeWidget = g_director->_wm->getActiveWidget();
+			if (activeWidget == nullptr || !activeWidget->isEditable())
+				g_director->_wm->setActiveWidget(widget);
+		}
+		break;
+
+	case kCastButton:
+		// note that we use _initialRect for the dimensions of the button;
+		// the values provided in the sprite bounding box are ignored
+		widget = new Graphics::MacButton(Graphics::MacButtonType(buttonType), getAlignment(), g_director->getCurrentWindow(), bbox.left, bbox.top, _initialRect.width(), _initialRect.height(), g_director->_wm, _ftext, macFont, getForeColor(), g_director->_wm->_colorWhite);
+		widget->_focusable = true;
+
+		((Graphics::MacButton *)widget)->setHilite(_hilite);
+		((Graphics::MacButton *)widget)->setCheckBoxType(g_director->getCurrentMovie()->_checkBoxType);
+		((Graphics::MacButton *)widget)->draw();
+		break;
+
+	default:
+		break;
+	}
+
+	delete macFont;
+	return widget;
+}
+
+void TextCastMember::importRTE(byte *text) {
+	//assert(rteList.size() == 3);
+	//child0 is probably font data.
+	//child1 is the raw text.
+	_rtext = _ptext = _ftext = Common::String((char*)text);
+	//child2 is positional?
+}
+
+void TextCastMember::setRawText(const Common::String &text) {
+	// Do nothing if text did not change
+	if (_rtext.equals(text))
+		return;
+
+	_rtext = text;
+	_ptext = Common::U32String(text);
+
+	// If text has changed, use the cached formatting from first STXT in this castmember.
+	Common::U32String formatting = Common::String::format("\001\016%04x%02x%04x%04x%04x%04x", _fontId, _textSlant, _fontSize, _fgpalinfo1, _fgpalinfo2, _fgpalinfo3);
+	_ftext = formatting + _ptext;
+	_modified = true;
+}
+
+// D4 dictionary book said this is line spacing
+int TextCastMember::getTextHeight() {
+	if (_widget)
+		return ((Graphics::MacText *)_widget)->getLineSpacing();
+	else
+		return _lineSpacing;
+	return 0;
+}
+
+// this should be amend when we have some where using this function
+int TextCastMember::getTextSize() {
+	if (_widget)
+		return ((Graphics::MacText *)_widget)->getTextSize();
+	else
+		return _fontSize;
+	return 0;
+}
+
+Common::U32String TextCastMember::getText() {
+	return _ptext;
+}
+
+Common::String TextCastMember::getRawText() {
+	return _rtext;
+}
+
+void TextCastMember::setTextSize(int textSize) {
+	if (_widget) {
+		((Graphics::MacText *)_widget)->setTextSize(textSize);
+		((Graphics::MacText *)_widget)->draw();
+	} else {
+		_fontSize = textSize;
+		_modified = true;
+	}
+}
+
+void TextCastMember::updateFromWidget(Graphics::MacWidget *widget) {
+	if (widget && _type == kCastText) {
+		_ptext = ((Graphics::MacText *)widget)->getEditedString();
+	}
+}
+
+Common::String TextCastMember::formatInfo() {
+	Common::String format = formatStringForDump(_ptext.encode());
+
+	return Common::String::format(
+		"initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, foreColor: %d, backColor: %d, editable: %d, text: \"%s\"",
+		_initialRect.width(), _initialRect.height(),
+		_initialRect.left, _initialRect.top,
+		_boundingRect.width(), _boundingRect.height(),
+		_boundingRect.left, _boundingRect.top,
+		getForeColor(), getBackColor(),
+		_editable, format.c_str()
+	);
+
+}
+
+bool TextCastMember::hasField(int field) {
+	switch (field) {
+	case kTheHilite:
+	case kTheText:
+	case kTheTextAlign:
+	case kTheTextFont:
+	case kTheTextHeight:
+	case kTheTextSize:
+	case kTheTextStyle:
+		return true;
+	default:
+		break;
+	}
+	return CastMember::hasField(field);
+}
+
+Datum TextCastMember::getField(int field) {
+	Datum d;
+
+	switch (field) {
+	case kTheHilite:
+		d = _hilite;
+		break;
+	case kTheText:
+		d = getText().encode(Common::kUtf8);
+		break;
+	case kTheTextAlign:
+		d.type = STRING;
+		switch (_textAlign) {
+		case kTextAlignLeft:
+			d.u.s = new Common::String("left");
+			break;
+		case kTextAlignCenter:
+			d.u.s = new Common::String("center");
+			break;
+		case kTextAlignRight:
+			d.u.s = new Common::String("right");
+			break;
+		default:
+			warning("TextCastMember::getField(): Invalid text align spec");
+			break;
+		}
+		break;
+	case kTheTextFont:
+		d.type = STRING;
+		d.u.s = new Common::String(g_director->_wm->_fontMan->getFontName(_fontId));
+		break;
+	case kTheTextHeight:
+		d = getTextHeight();
+		break;
+	case kTheTextSize:
+		d = getTextSize();
+		break;
+	case kTheTextStyle:
+		d = (int)_textSlant;
+		break;
+	default:
+		d = CastMember::getField(field);
+	}
+
+	return d;
+}
+
+bool TextCastMember::setField(int field, const Datum &d) {
+	Channel *toEdit = nullptr;
+
+	if (field == kTheTextFont || field == kTheTextSize || field == kTheTextStyle) {
+		Common::Array<Channel *> channels = g_director->getCurrentMovie()->getScore()->_channels;
+		for (uint i = 0; i < channels.size(); i++) {
+			if (channels[i]->_sprite->_cast == this) {
+				toEdit = channels[i];
+				break;
+			}
+		}
+		if (toEdit) {
+			Common::Rect bbox = toEdit->getBbox();
+			if (!toEdit->_widget)
+				toEdit->_widget = createWidget(bbox, toEdit, toEdit->_sprite->_spriteType);
+		}
+	}
+
+	switch (field) {
+	case kTheBackColor:
+		{
+			uint32 color = g_director->transformColor(d.asInt());
+			setColors(nullptr, &color);
+		}
+		return true;
+	case kTheForeColor:
+		{
+			uint32 color = g_director->transformColor(d.asInt());
+			setColors(&color, nullptr);
+		}
+		return true;
+	case kTheHilite:
+		// TODO: Understand how texts can be selected programmatically as well.
+		// since hilite won't affect text castmember, and we may have button info in text cast in D2/3. so don't check type here
+		_hilite = (bool)d.asInt();
+		_modified = true;
+		return true;
+		break;
+	case kTheText:
+		setRawText(d.asString());
+		return true;
+	case kTheTextAlign:
+		{
+			Common::String select = d.asString(true);
+			select.toLowercase();
+
+			TextAlignType align;
+			if (select == "\"left\"") {
+				align = kTextAlignLeft;
+			} else if (select == "\"center\"") {
+				align = kTextAlignCenter;
+			} else if (select == "\"right\"") {
+				align = kTextAlignRight;
+			} else {
+				warning("TextCastMember::setField(): Unknown text align spec: %s", d.asString(true).c_str());
+				break;
+			}
+
+			_textAlign = align;
+			_modified = true;
+	}
+		return true;
+	case kTheTextFont:
+		if (!toEdit) {
+			warning("Channel containing this CastMember %d doesn't exist", (int) _castId);
+			return false;
+		}
+		((Graphics::MacText *)toEdit->_widget)->enforceTextFont((uint16) g_director->_wm->_fontMan->getFontIdByName(d.asString()));
+		_ptext = ((Graphics::MacText *)toEdit->_widget)->getPlainText();
+		_ftext = ((Graphics::MacText *)toEdit->_widget)->getTextChunk(0, 0, -1, -1, true);
+		return true;
+	case kTheTextHeight:
+		_lineSpacing = d.asInt();
+		_modified = true;
+		return false;
+	case kTheTextSize:
+		if (!toEdit) {
+			warning("Channel containing this CastMember %d doesn't exist", (int) _castId);
+			return false;
+		}
+		((Graphics::MacText *)toEdit->_widget)->setTextSize(d.asInt());
+		_ptext = ((Graphics::MacText *)toEdit->_widget)->getPlainText();
+		_ftext = ((Graphics::MacText *)toEdit->_widget)->getTextChunk(0, 0, -1, -1, true);
+		return true;
+	case kTheTextStyle:
+		if (!toEdit) {
+			warning("Channel containing this CastMember %d doesn't exist", (int) _castId);
+			return false;
+		}
+		{
+			int slant = g_director->_wm->_fontMan->parseSlantFromName(d.asString());
+			((Graphics::MacText *)toEdit->_widget)->enforceTextSlant(slant);
+		}
+		_ptext = ((Graphics::MacText *)toEdit->_widget)->getPlainText();
+		_ftext = ((Graphics::MacText *)toEdit->_widget)->getTextChunk(0, 0, -1, -1, true);
+		return true;
+	default:
+		break;
+	}
+
+	return CastMember::setField(field, d);
+}
+
+bool TextCastMember::hasChunkField(int field) {
+	switch (field) {
+	case kTheForeColor:
+	case kTheTextFont:
+	case kTheTextHeight:
+	case kTheTextSize:
+	case kTheTextStyle:
+		return true;
+	default:
+		break;
+	}
+	return false;
+}
+
+Datum TextCastMember::getChunkField(int field, int start, int end) {
+	Datum d;
+
+	Graphics::MacText *macText = ((Graphics::MacText *)_widget);
+	if (!_widget)
+		warning("TextCastMember::getChunkField getting chunk field when there is no linked widget, returning the default value");
+
+	switch (field) {
+	case kTheForeColor:
+		if (_widget)
+			d.u.i = macText->getTextColor(start, end);
+		else
+			d.u.i = getForeColor();
+		break;
+	case kTheTextFont: {
+		int fontId;
+		if (_widget)
+			fontId = macText->getTextFont(start, end);
+		else
+			fontId = _fontId;
+
+		d.type = STRING;
+		d.u.s = new Common::String(g_director->_wm->_fontMan->getFontName(fontId));
+		break;
+		}
+	case kTheTextHeight:
+		warning("TextCastMember::getChunkField getting text height(line spacing) is not implemented yet, returning the default one");
+		d.u.i = _lineSpacing;
+		break;
+	case kTheTextSize:
+		if (_widget)
+			d.u.i = macText->getTextSize(start, end);
+		else
+			d.u.i = _fontSize;
+		break;
+	case kTheTextStyle:
+		if (_widget)
+			d.u.i = macText->getTextSlant(start, end);
+		else
+			d.u.i = _textSlant;
+		break;
+	default:
+		break;
+	}
+
+	return d;
+}
+
+bool TextCastMember::setChunkField(int field, int start, int end, const Datum &d) {
+	Graphics::MacText *macText = ((Graphics::MacText *)_widget);
+	if (!_widget)
+		warning("TextCastMember::setChunkField setting chunk field when there is no linked widget");
+
+	switch (field) {
+	case kTheForeColor:
+		if (_widget)
+			macText->setTextColor(d.asInt(), start, end);
+		return true;
+	case kTheTextFont:
+		if (_widget)
+			macText->setTextFont(d.asInt(), start, end);
+		return true;
+	case kTheTextHeight:
+		warning("TextCastMember::setChunkField setting text height(line spacing) is not implemented yet");
+		return false;
+	case kTheTextSize:
+		if (_widget)
+			macText->setTextSize(d.asInt(), start, end);
+		return true;
+	case kTheTextStyle:
+		if (_widget)
+			macText->setTextSlant(d.asInt(), start, end);
+		return true;
+	default:
+		break;
+	}
+
+	return false;
+}
+
+RTECastMember::RTECastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version)
+		: TextCastMember(cast, castId, stream, version) {
+
+	_type = kCastRTE;
+}
+
+void RTECastMember::loadChunks() {
+	//TODO: Actually load RTEs correctly, don't just make fake STXT.
+#if 0
+	Common::SeekableReadStream *rte1 = _movieArchive->getResource(res->children[child].tag, res->children[child].index);
+	byte *buffer = new byte[rte1->size() + 2];
+	rte1->read(buffer, rte1->size());
+	buffer[rte1->size()] = '\n';
+	buffer[rte1->size() + 1] = '\0';
+	_loadedText->getVal(id)->importRTE(buffer);
+
+	delete rte1;
+#endif
+}
+
+}
diff --git a/engines/director/castmember/text.h b/engines/director/castmember/text.h
new file mode 100644
index 00000000000..a579bb51e68
--- /dev/null
+++ b/engines/director/castmember/text.h
@@ -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 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef DIRECTOR_CASTMEMBER_TEXT_H
+#define DIRECTOR_CASTMEMBER_TEXT_H
+
+#include "director/castmember/castmember.h"
+
+namespace Director {
+
+class TextCastMember : public CastMember {
+public:
+	TextCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version, uint8 flags1 = 0, bool asButton = false);
+	void setColors(uint32 *fgcolor, uint32 *bgcolor) override;
+
+	Graphics::MacWidget *createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) override;
+
+	bool isEditable() override { return _editable; }
+	void setEditable(bool editable) override { _editable = editable; }
+	void updateFromWidget(Graphics::MacWidget *widget) override;
+	Graphics::TextAlign getAlignment();
+
+	uint32 getBackColor() override { return _bgcolor; }
+	void setBackColor(uint32 bgCol) override;
+	uint32 getForeColor() override { return _fgcolor; }
+	void setForeColor(uint32 fgCol) override;
+
+	bool hasField(int field) override;
+	Datum getField(int field) override;
+	bool setField(int field, const Datum &value) override;
+
+	bool hasChunkField(int field);
+	Datum getChunkField(int field, int start, int end);
+	bool setChunkField(int field, int start, int end, const Datum &value);
+
+	int getTextHeight();
+
+	int getTextSize();
+	void setTextSize(int textSize);
+
+	Common::String formatInfo() override;
+
+	SizeType _borderSize;
+	SizeType _gutterSize;
+	SizeType _boxShadow;
+	uint16 _maxHeight;
+	uint16 _textHeight;
+
+	uint32 _fontId;
+	uint16 _fontSize;
+	TextType _textType;
+	TextAlignType _textAlign;
+	SizeType _textShadow;
+	uint16 _scroll;
+	byte _textSlant;
+	byte _textFlags;
+	uint16 _bgpalinfo1, _bgpalinfo2, _bgpalinfo3;
+	uint16 _fgpalinfo1, _fgpalinfo2, _fgpalinfo3;
+	ButtonType _buttonType;
+	bool _editable;
+	int _lineSpacing;
+
+	Common::U32String _ftext;
+	Common::U32String _ptext;
+	Common::String _rtext;
+	void importStxt(const Stxt *stxt);
+	void importRTE(byte *text);
+
+	Common::U32String getText();
+	Common::String getRawText();
+	void setRawText(const Common::String &text);
+
+private:
+	uint32 _bgcolor;
+	uint32 _fgcolor;
+};
+
+class RTECastMember : public TextCastMember {
+public:
+	RTECastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version);
+
+	void loadChunks();
+};
+
+} // End of namespace Director
+
+#endif
diff --git a/engines/director/channel.cpp b/engines/director/channel.cpp
index d4a43dc5136..89d25373241 100644
--- a/engines/director/channel.cpp
+++ b/engines/director/channel.cpp
@@ -26,9 +26,12 @@
 #include "director/cast.h"
 #include "director/channel.h"
 #include "director/sprite.h"
-#include "director/castmember.h"
 #include "director/types.h"
 #include "director/window.h"
+#include "director/castmember/castmember.h"
+#include "director/castmember/bitmap.h"
+#include "director/castmember/digitalvideo.h"
+#include "director/castmember/filmloop.h"
 
 #include "graphics/macgui/mactext.h"
 #include "graphics/macgui/macbutton.h"
diff --git a/engines/director/cursor.cpp b/engines/director/cursor.cpp
index f705ce3678e..df7b63e1c44 100644
--- a/engines/director/cursor.cpp
+++ b/engines/director/cursor.cpp
@@ -26,7 +26,7 @@
 #include "director/cast.h"
 #include "director/cursor.h"
 #include "director/movie.h"
-#include "director/castmember.h"
+#include "director/castmember/bitmap.h"
 #include "director/picture.h"
 
 namespace Director {
diff --git a/engines/director/debugger.cpp b/engines/director/debugger.cpp
index 17fffd89b03..248e91ddfdc 100644
--- a/engines/director/debugger.cpp
+++ b/engines/director/debugger.cpp
@@ -23,8 +23,8 @@
 #include "common/platform.h"
 #include "director/director.h"
 #include "director/debugger.h"
+#include "director/archive.h"
 #include "director/cast.h"
-#include "director/castmember.h"
 #include "director/frame.h"
 #include "director/movie.h"
 #include "director/score.h"
diff --git a/engines/director/events.cpp b/engines/director/events.cpp
index c4c71be273b..e22dc8ce464 100644
--- a/engines/director/events.cpp
+++ b/engines/director/events.cpp
@@ -35,7 +35,7 @@
 #include "director/channel.h"
 #include "director/sprite.h"
 #include "director/window.h"
-#include "director/castmember.h"
+#include "director/castmember/castmember.h"
 #include "director/lingo/lingo.h"
 
 namespace Director {
diff --git a/engines/director/graphics.cpp b/engines/director/graphics.cpp
index cf5d065521d..575b79a9577 100644
--- a/engines/director/graphics.cpp
+++ b/engines/director/graphics.cpp
@@ -25,11 +25,11 @@
 
 #include "director/director.h"
 #include "director/cast.h"
-#include "director/castmember.h"
 #include "director/movie.h"
 #include "director/images.h"
 #include "director/picture.h"
 #include "director/window.h"
+#include "director/castmember/bitmap.h"
 
 namespace Director {
 
diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp
index 64dd09278fd..66ecea3ecdb 100644
--- a/engines/director/lingo/lingo-builtins.cpp
+++ b/engines/director/lingo/lingo-builtins.cpp
@@ -30,7 +30,6 @@
 
 #include "director/director.h"
 #include "director/cast.h"
-#include "director/castmember.h"
 #include "director/frame.h"
 #include "director/movie.h"
 #include "director/score.h"
@@ -41,6 +40,10 @@
 #include "director/window.h"
 #include "director/stxt.h"
 #include "director/util.h"
+#include "director/castmember/castmember.h"
+#include "director/castmember/bitmap.h"
+#include "director/castmember/palette.h"
+#include "director/castmember/text.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-builtins.h"
 #include "director/lingo/lingo-code.h"
diff --git a/engines/director/lingo/lingo-bytecode.cpp b/engines/director/lingo/lingo-bytecode.cpp
index adb6b30e540..051adf34bff 100644
--- a/engines/director/lingo/lingo-bytecode.cpp
+++ b/engines/director/lingo/lingo-bytecode.cpp
@@ -26,10 +26,11 @@
 
 #include "director/director.h"
 #include "director/cast.h"
-#include "director/castmember.h"
 #include "director/movie.h"
 #include "director/util.h"
 #include "director/window.h"
+#include "director/castmember/castmember.h"
+#include "director/castmember/script.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-code.h"
 #include "director/lingo/lingo-codegen.h"
diff --git a/engines/director/lingo/lingo-code.cpp b/engines/director/lingo/lingo-code.cpp
index 7fdaf18e965..21243a174d9 100644
--- a/engines/director/lingo/lingo-code.cpp
+++ b/engines/director/lingo/lingo-code.cpp
@@ -46,7 +46,6 @@
 #include "graphics/macgui/mactext.h"
 
 #include "director/director.h"
-#include "director/castmember.h"
 #include "director/movie.h"
 #include "director/score.h"
 #include "director/sprite.h"
@@ -54,6 +53,7 @@
 #include "director/cursor.h"
 #include "director/channel.h"
 #include "director/util.h"
+#include "director/castmember/castmember.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-builtins.h"
 #include "director/lingo/lingo-code.h"
diff --git a/engines/director/lingo/lingo-funcs.cpp b/engines/director/lingo/lingo-funcs.cpp
index e0add1a92f0..58e8c883609 100644
--- a/engines/director/lingo/lingo-funcs.cpp
+++ b/engines/director/lingo/lingo-funcs.cpp
@@ -28,7 +28,7 @@
 #include "graphics/macgui/macwindowmanager.h"
 
 #include "director/director.h"
-#include "director/castmember.h"
+#include "director/archive.h"
 #include "director/cursor.h"
 #include "director/movie.h"
 #include "director/score.h"
diff --git a/engines/director/lingo/lingo-object.cpp b/engines/director/lingo/lingo-object.cpp
index b4394714a79..b925208091c 100644
--- a/engines/director/lingo/lingo-object.cpp
+++ b/engines/director/lingo/lingo-object.cpp
@@ -24,11 +24,7 @@
 #include "graphics/macgui/mactext.h"
 
 #include "director/director.h"
-#include "director/cast.h"
-#include "director/channel.h"
-#include "director/castmember.h"
 #include "director/movie.h"
-#include "director/score.h"
 #include "director/window.h"
 #include "director/util.h"
 #include "director/lingo/lingo.h"
@@ -676,621 +672,4 @@ void LM::m_moveToFront(int nargs) {
 	g_lingo->dropStack(nargs);
 }
 
-// CastMember
-
-bool CastMember::hasProp(const Common::String &propName) {
-	Common::String fieldName = Common::String::format("%d%s", kTheCast, propName.c_str());
-	return g_lingo->_theEntityFields.contains(fieldName) && hasField(g_lingo->_theEntityFields[fieldName]->field);
-}
-
-Datum CastMember::getProp(const Common::String &propName) {
-	Common::String fieldName = Common::String::format("%d%s", kTheCast, propName.c_str());
-	if (g_lingo->_theEntityFields.contains(fieldName)) {
-		return getField(g_lingo->_theEntityFields[fieldName]->field);
-	}
-
-	warning("CastMember::getProp: unknown property '%s'", propName.c_str());
-	return Datum();
-}
-
-bool CastMember::setProp(const Common::String &propName, const Datum &value) {
-	Common::String fieldName = Common::String::format("%d%s", kTheCast, propName.c_str());
-	if (g_lingo->_theEntityFields.contains(fieldName)) {
-		return setField(g_lingo->_theEntityFields[fieldName]->field, value);
-	}
-
-	warning("CastMember::setProp: unknown property '%s'", propName.c_str());
-	return false;
-}
-
-bool CastMember::hasField(int field) {
-	switch (field) {
-	case kTheBackColor:
-	case kTheCastType:
-	case kTheFileName:
-	case kTheForeColor:
-	case kTheHeight:
-	case kTheLoaded:
-	case kTheModified:
-	case kTheName:
-	case kTheNumber:
-	case kTheRect:
-	case kThePurgePriority:
-	case kTheScriptText:
-	case kTheSize:
-	case kTheWidth:
-		return true;
-	default:
-		break;
-	}
-	return false;
-}
-
-Datum CastMember::getField(int field) {
-	Datum d;
-
-	CastMemberInfo *castInfo = _cast->getCastMemberInfo(_castId);
-	if (!castInfo)
-		warning("CastMember::getField(): CastMember info for %d not found", _castId);
-
-	switch (field) {
-	case kTheBackColor:
-		d = (int)getBackColor();
-		break;
-	case kTheCastType:
-		d.type = SYMBOL;
-		d.u.s = new Common::String(castType2str(_type));
-		break;
-	case kTheFileName:
-		if (castInfo)
-			d = Datum(castInfo->directory + g_director->_dirSeparator + castInfo->fileName);
-		break;
-	case kTheForeColor:
-		d = (int)getForeColor();
-		break;
-	case kTheHeight:
-		d = _cast->getCastMemberInitialRect(_castId).height();
-		break;
-	case kTheLoaded:
-		d = 1; // Not loaded handled in Lingo::getTheCast
-		break;
-	case kTheModified:
-		d = (int)_isChanged;
-		break;
-	case kTheName:
-		if (castInfo)
-			d = Datum(castInfo->name);
-		break;
-	case kTheNumber:
-		d = _castId;
-		break;
-	case kTheRect:
-		// not sure get the initial rect would be fine to castmember
-		d = Datum(_cast->getCastMember(_castId)->_initialRect);
-		break;
-	case kThePurgePriority:
-		d = _purgePriority;
-		break;
-	case kTheScriptText:
-		if (castInfo)
-			d = Datum(castInfo->script);
-		break;
-	case kTheSize:
-		d = (int)_size;
-		break;
-	case kTheWidth:
-		d = _cast->getCastMemberInitialRect(_castId).width();
-		break;
-	default:
-		warning("CastMember::getField(): Unprocessed getting field \"%s\" of cast %d", g_lingo->field2str(field), _castId);
-	//TODO find out about String fields
-	}
-
-	return d;
-}
-
-bool CastMember::setField(int field, const Datum &d) {
-	CastMemberInfo *castInfo = _cast->getCastMemberInfo(_castId);
-
-	switch (field) {
-	case kTheBackColor:
-		_cast->getCastMember(_castId)->setBackColor(d.asInt());
-		return true;
-	case kTheCastType:
-		warning("BUILDBOT: CastMember::setField(): Attempt to set read-only field %s of cast %d", g_lingo->entity2str(field), _castId);
-		return false;
-	case kTheFileName:
-		if (!castInfo) {
-			warning("CastMember::setField(): CastMember info for %d not found", _castId);
-			return false;
-		}
-		castInfo->fileName = d.asString();
-		return true;
-	case kTheForeColor:
-		_cast->getCastMember(_castId)->setForeColor(d.asInt());
-		return true;
-	case kTheHeight:
-		warning("BUILDBOT: CastMember::setField(): Attempt to set read-only field \"%s\" of cast %d", g_lingo->field2str(field), _castId);
-		return false;
-	case kTheName:
-		if (!castInfo) {
-			warning("CastMember::setField(): CastMember info for %d not found", _castId);
-			return false;
-		}
-		castInfo->name = d.asString();
-		return true;
-	case kTheRect:
-		warning("CastMember::setField(): Attempt to set read-only field \"%s\" of cast %d", g_lingo->field2str(field), _castId);
-		return false;
-	case kThePurgePriority:
-		_purgePriority = CLIP<int>(d.asInt(), 0, 3);
-		return true;
-	case kTheScriptText:
-		if (!castInfo) {
-			warning("CastMember::setField(): CastMember info for %d not found", _castId);
-			return false;
-		}
-		_cast->_lingoArchive->replaceCode(*d.u.s, kCastScript, _castId);
-		castInfo->script = d.asString();
-		return true;
-	case kTheWidth:
-		warning("BUILDBOT: CastMember::setField(): Attempt to set read-only field \"%s\" of cast %d", g_lingo->field2str(field), _castId);
-		return false;
-	default:
-		warning("CastMember::setField(): Unprocessed setting field \"%s\" of cast %d", g_lingo->field2str(field), _castId);
-	}
-
-	return false;
-}
-
-bool DigitalVideoCastMember::hasField(int field) {
-	switch (field) {
-	case kTheCenter:
-	case kTheController:
-	case kTheCrop:
-	case kTheDirectToStage:
-	case kTheDuration:
-	case kTheFrameRate:
-	case kTheLoop:
-	case kTheMovieRate:
-	case kTheMovieTime:
-	case kThePausedAtStart:
-	case kThePreLoad:
-	case kTheSound:
-	case kTheVideo:
-	case kTheVolume:
-		return true;
-	default:
-		break;
-	}
-	return CastMember::hasField(field);
-}
-
-Datum DigitalVideoCastMember::getField(int field) {
-	Datum d;
-
-	switch (field) {
-	case kTheCenter:
-		d = _center;
-		break;
-	case kTheController:
-		d = _showControls;
-		break;
-	case kTheCrop:
-		d = _crop;
-		break;
-	case kTheDirectToStage:
-		d = _directToStage;
-		break;
-	case kTheDuration:
-		// sometimes, we will get duration before we start video.
-		// _duration is initialized in startVideo, thus we will not get the correct number.
-		d = (int)getDuration();
-		break;
-	case kTheFrameRate:
-		d = _frameRate;
-		break;
-	case kTheLoop:
-		d = _looping;
-		break;
-	case kThePausedAtStart:
-		d = _pausedAtStart;
-		break;
-	case kThePreLoad:
-		d = _preload;
-		break;
-	case kTheSound:
-		d = _enableSound;
-		break;
-	case kTheVideo:
-		d = _enableVideo;
-		break;
-	default:
-		d = CastMember::getField(field);
-	}
-
-	return d;
-}
-
-bool DigitalVideoCastMember::setField(int field, const Datum &d) {
-	switch (field) {
-	case kTheCenter:
-		_center = (bool)d.asInt();
-		return true;
-	case kTheController:
-		_showControls = (bool)d.asInt();
-		return true;
-	case kTheCrop:
-		_crop = (bool)d.asInt();
-		return true;
-	case kTheDirectToStage:
-		_directToStage = (bool)d.asInt();
-		return true;
-	case kTheDuration:
-		warning("DigitalVideoCastMember::setField(): Attempt to set read-only field %s of cast %d", g_lingo->entity2str(field), _castId);
-		return false;
-	case kTheFrameRate:
-		_frameRate = d.asInt();
-		setFrameRate(d.asInt());
-		return true;
-	case kTheLoop:
-		_looping = (bool)d.asInt();
-		if (_looping && _channel && _channel->_movieRate == 0.0) {
-			setMovieRate(1.0);
-		}
-		return true;
-	case kThePausedAtStart:
-		_pausedAtStart = (bool)d.asInt();
-		return true;
-	case kThePreLoad:
-		_preload = (bool)d.asInt();
-		return true;
-	case kTheSound:
-		_enableSound = (bool)d.asInt();
-		return true;
-	case kTheVideo:
-		_enableVideo = (bool)d.asInt();
-		return true;
-	default:
-		break;
-	}
-
-	return CastMember::setField(field, d);
-}
-
-bool BitmapCastMember::hasField(int field) {
-	switch (field) {
-	case kTheDepth:
-	case kTheRegPoint:
-	case kThePalette:
-	case kThePicture:
-		return true;
-	default:
-		break;
-	}
-	return CastMember::hasField(field);
-}
-
-Datum BitmapCastMember::getField(int field) {
-	Datum d;
-
-	switch (field) {
-	case kTheDepth:
-		d = _bitsPerPixel;
-		break;
-	case kTheRegPoint:
-		d.type = POINT;
-		d.u.farr = new FArray;
-		d.u.farr->arr.push_back(_regX);
-		d.u.farr->arr.push_back(_regY);
-		break;
-	case kThePalette:
-		d = _clut;
-		break;
-	case kThePicture:
-		d.type = PICTUREREF;
-		d.u.picture = getPicture();
-		break;
-	default:
-		d = CastMember::getField(field);
-	}
-
-	return d;
-}
-
-bool BitmapCastMember::setField(int field, const Datum &d) {
-	switch (field) {
-	case kTheDepth:
-		warning("BitmapCastMember::setField(): Attempt to set read-only field %s of cast %d", g_lingo->field2str(field), _castId);
-		return false;
-	case kTheRegPoint:
-		if (d.type == POINT || (d.type == ARRAY && d.u.farr->arr.size() >= 2)) {
-			Score *score = g_director->getCurrentMovie()->getScore();
-			score->invalidateRectsForMember(this);
-			_regX = d.u.farr->arr[0].asInt();
-			_regY = d.u.farr->arr[1].asInt();
-			_modified = true;
-		} else {
-			warning("BitmapCastMember::setField(): Wrong Datum type %d for kTheRegPoint", d.type);
-			return false;
-		}
-		return true;
-	case kThePalette:
-		_clut = d.asInt();
-		return true;
-	case kThePicture:
-		if (d.type == PICTUREREF && d.u.picture != nullptr) {
-			setPicture(*d.u.picture);
-			return true;
-		} else {
-			warning("BitmapCastMember::setField(): Wrong Datum type %d for kThePicture (or nullptr)", d.type);
-		}
-		return false;
-	default:
-		break;
-	}
-
-	return CastMember::setField(field, d);
-}
-
-bool TextCastMember::hasField(int field) {
-	switch (field) {
-	case kTheHilite:
-	case kTheText:
-	case kTheTextAlign:
-	case kTheTextFont:
-	case kTheTextHeight:
-	case kTheTextSize:
-	case kTheTextStyle:
-		return true;
-	default:
-		break;
-	}
-	return CastMember::hasField(field);
-}
-
-Datum TextCastMember::getField(int field) {
-	Datum d;
-
-	switch (field) {
-	case kTheHilite:
-		d = _hilite;
-		break;
-	case kTheText:
-		d = getText().encode(Common::kUtf8);
-		break;
-	case kTheTextAlign:
-		d.type = STRING;
-		switch (_textAlign) {
-		case kTextAlignLeft:
-			d.u.s = new Common::String("left");
-			break;
-		case kTextAlignCenter:
-			d.u.s = new Common::String("center");
-			break;
-		case kTextAlignRight:
-			d.u.s = new Common::String("right");
-			break;
-		default:
-			warning("TextCastMember::getField(): Invalid text align spec");
-			break;
-		}
-		break;
-	case kTheTextFont:
-		d.type = STRING;
-		d.u.s = new Common::String(g_director->_wm->_fontMan->getFontName(_fontId));
-		break;
-	case kTheTextHeight:
-		d = getTextHeight();
-		break;
-	case kTheTextSize:
-		d = getTextSize();
-		break;
-	case kTheTextStyle:
-		d = (int)_textSlant;
-		break;
-	default:
-		d = CastMember::getField(field);
-	}
-
-	return d;
-}
-
-bool TextCastMember::setField(int field, const Datum &d) {
-	Channel *toEdit = nullptr;
-
-	if (field == kTheTextFont || field == kTheTextSize || field == kTheTextStyle) {
-		Common::Array<Channel *> channels = g_director->getCurrentMovie()->getScore()->_channels;
-		for (uint i = 0; i < channels.size(); i++) {
-			if (channels[i]->_sprite->_cast == this) {
-				toEdit = channels[i];
-				break;
-			}
-		}
-		if (toEdit) {
-			Common::Rect bbox = toEdit->getBbox();
-			if (!toEdit->_widget)
-				toEdit->_widget = createWidget(bbox, toEdit, toEdit->_sprite->_spriteType);
-		}
-	}
-
-	switch (field) {
-	case kTheBackColor:
-		{
-			uint32 color = g_director->transformColor(d.asInt());
-			setColors(nullptr, &color);
-		}
-		return true;
-	case kTheForeColor:
-		{
-			uint32 color = g_director->transformColor(d.asInt());
-			setColors(&color, nullptr);
-		}
-		return true;
-	case kTheHilite:
-		// TODO: Understand how texts can be selected programmatically as well.
-		// since hilite won't affect text castmember, and we may have button info in text cast in D2/3. so don't check type here
-		_hilite = (bool)d.asInt();
-		_modified = true;
-		return true;
-		break;
-	case kTheText:
-		setRawText(d.asString());
-		return true;
-	case kTheTextAlign:
-		{
-			Common::String select = d.asString(true);
-			select.toLowercase();
-
-			TextAlignType align;
-			if (select == "\"left\"") {
-				align = kTextAlignLeft;
-			} else if (select == "\"center\"") {
-				align = kTextAlignCenter;
-			} else if (select == "\"right\"") {
-				align = kTextAlignRight;
-			} else {
-				warning("TextCastMember::setField(): Unknown text align spec: %s", d.asString(true).c_str());
-				break;
-			}
-
-			_textAlign = align;
-			_modified = true;
-	}
-		return true;
-	case kTheTextFont:
-		if (!toEdit) {
-			warning("Channel containing this CastMember %d doesn't exist", (int) _castId);
-			return false;
-		}
-		((Graphics::MacText *)toEdit->_widget)->enforceTextFont((uint16) g_director->_wm->_fontMan->getFontIdByName(d.asString()));
-		_ptext = ((Graphics::MacText *)toEdit->_widget)->getPlainText();
-		_ftext = ((Graphics::MacText *)toEdit->_widget)->getTextChunk(0, 0, -1, -1, true);
-		return true;
-	case kTheTextHeight:
-		_lineSpacing = d.asInt();
-		_modified = true;
-		return false;
-	case kTheTextSize:
-		if (!toEdit) {
-			warning("Channel containing this CastMember %d doesn't exist", (int) _castId);
-			return false;
-		}
-		((Graphics::MacText *)toEdit->_widget)->setTextSize(d.asInt());
-		_ptext = ((Graphics::MacText *)toEdit->_widget)->getPlainText();
-		_ftext = ((Graphics::MacText *)toEdit->_widget)->getTextChunk(0, 0, -1, -1, true);
-		return true;
-	case kTheTextStyle:
-		if (!toEdit) {
-			warning("Channel containing this CastMember %d doesn't exist", (int) _castId);
-			return false;
-		}
-		{
-			int slant = g_director->_wm->_fontMan->parseSlantFromName(d.asString());
-			((Graphics::MacText *)toEdit->_widget)->enforceTextSlant(slant);
-		}
-		_ptext = ((Graphics::MacText *)toEdit->_widget)->getPlainText();
-		_ftext = ((Graphics::MacText *)toEdit->_widget)->getTextChunk(0, 0, -1, -1, true);
-		return true;
-	default:
-		break;
-	}
-
-	return CastMember::setField(field, d);
-}
-
-bool TextCastMember::hasChunkField(int field) {
-	switch (field) {
-	case kTheForeColor:
-	case kTheTextFont:
-	case kTheTextHeight:
-	case kTheTextSize:
-	case kTheTextStyle:
-		return true;
-	default:
-		break;
-	}
-	return false;
-}
-
-Datum TextCastMember::getChunkField(int field, int start, int end) {
-	Datum d;
-
-	Graphics::MacText *macText = ((Graphics::MacText *)_widget);
-	if (!_widget)
-		warning("TextCastMember::getChunkField getting chunk field when there is no linked widget, returning the default value");
-
-	switch (field) {
-	case kTheForeColor:
-		if (_widget)
-			d.u.i = macText->getTextColor(start, end);
-		else
-			d.u.i = getForeColor();
-		break;
-	case kTheTextFont: {
-		int fontId;
-		if (_widget)
-			fontId = macText->getTextFont(start, end);
-		else
-			fontId = _fontId;
-
-		d.type = STRING;
-		d.u.s = new Common::String(g_director->_wm->_fontMan->getFontName(fontId));
-		break;
-		}
-	case kTheTextHeight:
-		warning("TextCastMember::getChunkField getting text height(line spacing) is not implemented yet, returning the default one");
-		d.u.i = _lineSpacing;
-		break;
-	case kTheTextSize:
-		if (_widget)
-			d.u.i = macText->getTextSize(start, end);
-		else
-			d.u.i = _fontSize;
-		break;
-	case kTheTextStyle:
-		if (_widget)
-			d.u.i = macText->getTextSlant(start, end);
-		else
-			d.u.i = _textSlant;
-		break;
-	default:
-		break;
-	}
-
-	return d;
-}
-
-bool TextCastMember::setChunkField(int field, int start, int end, const Datum &d) {
-	Graphics::MacText *macText = ((Graphics::MacText *)_widget);
-	if (!_widget)
-		warning("TextCastMember::setChunkField setting chunk field when there is no linked widget");
-
-	switch (field) {
-	case kTheForeColor:
-		if (_widget)
-			macText->setTextColor(d.asInt(), start, end);
-		return true;
-	case kTheTextFont:
-		if (_widget)
-			macText->setTextFont(d.asInt(), start, end);
-		return true;
-	case kTheTextHeight:
-		warning("TextCastMember::setChunkField setting text height(line spacing) is not implemented yet");
-		return false;
-	case kTheTextSize:
-		if (_widget)
-			macText->setTextSize(d.asInt(), start, end);
-		return true;
-	case kTheTextStyle:
-		if (_widget)
-			macText->setTextSlant(d.asInt(), start, end);
-		return true;
-	default:
-		break;
-	}
-
-	return false;
-}
-
 } // End of namespace Director
diff --git a/engines/director/lingo/lingo-the.cpp b/engines/director/lingo/lingo-the.cpp
index cd249334012..922a682fe8c 100644
--- a/engines/director/lingo/lingo-the.cpp
+++ b/engines/director/lingo/lingo-the.cpp
@@ -27,7 +27,6 @@
 
 #include "director/director.h"
 #include "director/cast.h"
-#include "director/castmember.h"
 #include "director/cursor.h"
 #include "director/channel.h"
 #include "director/frame.h"
@@ -36,6 +35,9 @@
 #include "director/sprite.h"
 #include "director/score.h"
 #include "director/window.h"
+#include "director/castmember/castmember.h"
+#include "director/castmember/digitalvideo.h"
+#include "director/castmember/text.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-builtins.h"
 #include "director/lingo/lingo-code.h"
diff --git a/engines/director/lingo/lingo.cpp b/engines/director/lingo/lingo.cpp
index bea80135316..b645712a606 100644
--- a/engines/director/lingo/lingo.cpp
+++ b/engines/director/lingo/lingo.cpp
@@ -26,7 +26,6 @@
 
 #include "director/director.h"
 #include "director/cast.h"
-#include "director/castmember.h"
 #include "director/frame.h"
 #include "director/movie.h"
 #include "director/picture.h"
@@ -34,6 +33,8 @@
 #include "director/sprite.h"
 #include "director/window.h"
 #include "director/util.h"
+#include "director/castmember/castmember.h"
+#include "director/castmember/text.h"
 
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-ast.h"
diff --git a/engines/director/module.mk b/engines/director/module.mk
index 46f413d11f9..c47a2051064 100644
--- a/engines/director/module.mk
+++ b/engines/director/module.mk
@@ -3,7 +3,6 @@ MODULE := engines/director
 MODULE_OBJS = \
 	archive.o \
 	cast.o \
-	castmember.o \
 	channel.o \
 	cursor.o \
 	director.o \
@@ -27,6 +26,16 @@ MODULE_OBJS = \
 	types.o \
 	util.o \
 	window.o \
+	castmember/castmember.o \
+	castmember/bitmap.o \
+	castmember/digitalvideo.o \
+	castmember/filmloop.o \
+	castmember/movie.o \
+	castmember/palette.o \
+	castmember/script.o \
+	castmember/shape.o \
+	castmember/sound.o \
+	castmember/text.o \
 	lingo/lingo.o \
 	lingo/lingo-builtins.o \
 	lingo/lingo-bytecode.o \
diff --git a/engines/director/resource.cpp b/engines/director/resource.cpp
index 85684872086..c218c5547af 100644
--- a/engines/director/resource.cpp
+++ b/engines/director/resource.cpp
@@ -30,8 +30,8 @@
 #include "graphics/wincursor.h"
 
 #include "director/director.h"
+#include "director/archive.h"
 #include "director/cast.h"
-#include "director/castmember.h"
 #include "director/movie.h"
 #include "director/window.h"
 #include "director/lingo/lingo.h"
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index 46361104600..7aba55f2c71 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -38,7 +38,6 @@
 
 #include "director/director.h"
 #include "director/cast.h"
-#include "director/castmember.h"
 #include "director/score.h"
 #include "director/frame.h"
 #include "director/movie.h"
@@ -48,6 +47,8 @@
 #include "director/sprite.h"
 #include "director/window.h"
 #include "director/util.h"
+#include "director/castmember/castmember.h"
+#include "director/castmember/palette.h"
 #include "director/lingo/lingo.h"
 
 namespace Director {
diff --git a/engines/director/score.h b/engines/director/score.h
index 28b556fcd45..f4a508084f5 100644
--- a/engines/director/score.h
+++ b/engines/director/score.h
@@ -46,7 +46,6 @@ class Archive;
 class DirectorEngine;
 class DirectorSound;
 class Frame;
-struct Label;
 class Movie;
 struct Resource;
 class Cursor;
@@ -60,6 +59,13 @@ enum RenderMode {
 	kRenderForceUpdate
 };
 
+struct Label {
+	Common::String comment;
+	Common::String name;
+	uint16 number;
+	Label(Common::String name1, uint16 number1, Common::String comment1) { name = name1; number = number1; comment = comment1;}
+};
+
 class Score {
 public:
 	Score(Movie *movie);
diff --git a/engines/director/sound.cpp b/engines/director/sound.cpp
index 3c5210adea9..3869cfb10a5 100644
--- a/engines/director/sound.cpp
+++ b/engines/director/sound.cpp
@@ -35,10 +35,10 @@
 
 #include "director/director.h"
 #include "director/movie.h"
-#include "director/castmember.h"
 #include "director/sound.h"
 #include "director/util.h"
 #include "director/window.h"
+#include "director/castmember/sound.h"
 
 namespace Director {
 
diff --git a/engines/director/sprite.cpp b/engines/director/sprite.cpp
index 745172ca217..a3c33b5cbcc 100644
--- a/engines/director/sprite.cpp
+++ b/engines/director/sprite.cpp
@@ -22,11 +22,13 @@
 #include "graphics/macgui/macwidget.h"
 
 #include "director/director.h"
-#include "director/castmember.h"
 #include "director/frame.h"
 #include "director/movie.h"
 #include "director/score.h"
 #include "director/sprite.h"
+#include "director/castmember/castmember.h"
+#include "director/castmember/bitmap.h"
+#include "director/castmember/shape.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
 
diff --git a/engines/director/window.cpp b/engines/director/window.cpp
index 45d280f137d..bafb880a168 100644
--- a/engines/director/window.cpp
+++ b/engines/director/window.cpp
@@ -27,17 +27,18 @@
 #include "graphics/macgui/macwindowmanager.h"
 
 #include "director/director.h"
+#include "director/archive.h"
 #include "director/cast.h"
 #include "director/lingo/lingo.h"
 #include "director/movie.h"
 #include "director/window.h"
 #include "director/score.h"
-#include "director/castmember.h"
 #include "director/cursor.h"
 #include "director/channel.h"
 #include "director/sound.h"
 #include "director/sprite.h"
 #include "director/util.h"
+#include "director/castmember/castmember.h"
 
 namespace Director {
 


Commit: e289ea58962870a719179eb736c7b678eec6d707
    https://github.com/scummvm/scummvm/commit/e289ea58962870a719179eb736c7b678eec6d707
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: Remove shared cast check from Cast::loadBitmapData()

This is a holdover from the original code, where the cast members
were loaded as part of the Score. Cast is now per-movie, so all the
resources should be in the one place.

Changed paths:
    engines/director/cast.cpp


diff --git a/engines/director/cast.cpp b/engines/director/cast.cpp
index 301b26061fc..65bd24a9ce6 100644
--- a/engines/director/cast.cpp
+++ b/engines/director/cast.cpp
@@ -691,7 +691,6 @@ void Cast::loadBitmapData(int key, BitmapCastMember *bitmapCast) {
 	uint32 tag = bitmapCast->_tag;
 	uint16 imgId = key;
 	uint16 realId = 0;
-	Cast *sharedCast = _movie ? _movie->getSharedCast() : nullptr;
 
 	Image::ImageDecoder *img = nullptr;
 	Common::SeekableReadStream *pic = nullptr;
@@ -703,8 +702,6 @@ void Cast::loadBitmapData(int key, BitmapCastMember *bitmapCast) {
 
 			if (_castArchive->hasResource(tag, imgId))
 				pic = _castArchive->getResource(tag, imgId);
-			else if (sharedCast && sharedCast->getArchive()->hasResource(tag, imgId))
-				pic = sharedCast->getArchive()->getResource(tag, imgId);
 		}
 
 		if ((pic == nullptr || pic->size() == 0)
@@ -766,10 +763,6 @@ void Cast::loadBitmapData(int key, BitmapCastMember *bitmapCast) {
 			bitmapCast->_tag = tag = ((BitmapCastMember *)_loadedCast->getVal(imgId))->_tag;
 			realId = imgId + _castIDoffset;
 			pic = _castArchive->getResource(tag, realId);
-		} else if (sharedCast && sharedCast->_loadedCast && sharedCast->_loadedCast->contains(imgId)) {
-			bitmapCast->_tag = tag = ((BitmapCastMember *)sharedCast->_loadedCast->getVal(imgId))->_tag;
-			realId = imgId + sharedCast->_castIDoffset;
-			pic = sharedCast->getArchive()->getResource(tag, realId);
 		}
 	}
 


Commit: 1fd0692f6b00b61daa435ce5b30969b8b7a25b17
    https://github.com/scummvm/scummvm/commit/1fd0692f6b00b61daa435ce5b30969b8b7a25b17
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: Move load operations inside cast member

Changed paths:
    engines/director/cast.cpp
    engines/director/cast.h
    engines/director/castmember/bitmap.cpp
    engines/director/castmember/bitmap.h
    engines/director/castmember/castmember.cpp
    engines/director/castmember/castmember.h
    engines/director/castmember/filmloop.cpp
    engines/director/castmember/filmloop.h
    engines/director/castmember/palette.cpp
    engines/director/castmember/palette.h
    engines/director/castmember/sound.cpp
    engines/director/castmember/sound.h
    engines/director/castmember/text.cpp
    engines/director/castmember/text.h


diff --git a/engines/director/cast.cpp b/engines/director/cast.cpp
index 65bd24a9ce6..e0e6ebafcf7 100644
--- a/engines/director/cast.cpp
+++ b/engines/director/cast.cpp
@@ -23,17 +23,14 @@
 #include "common/file.h"
 #include "common/macresman.h"
 #include "common/memstream.h"
+#include "common/stream.h"
 #include "common/substream.h"
 
 #include "graphics/macgui/macfontmanager.h"
 #include "graphics/macgui/macwindowmanager.h"
-#include "image/bmp.h"
-#include "image/jpeg.h"
-#include "image/pict.h"
 
 #include "director/director.h"
 #include "director/cast.h"
-#include "director/images.h"
 #include "director/movie.h"
 #include "director/score.h"
 #include "director/sound.h"
@@ -625,240 +622,6 @@ void Cast::loadCast() {
 	loadCastMemberData();
 }
 
-void Cast::loadStxtData(int key, TextCastMember *member) {
-	uint stxtid;
-	if (_version >= kFileVer400 && member->_children.size() > 0)
-		stxtid = member->_children[0].index;
-	else
-		stxtid = key;
-
-	if (_loadedStxts->contains(stxtid)) {
-		const Stxt *stxt = _loadedStxts->getVal(stxtid);
-		member->importStxt(stxt);
-		member->_size = stxt->_size;
-	} else {
-		warning("Cast::loadStxtData: stxtid %i isn't loaded", stxtid);
-	}
-}
-
-void Cast::loadPaletteData(int key, PaletteCastMember *member) {
-	// TODO: Verify how palettes work in >D4 versions
-	int paletteId = 0;
-	if (_version >= kFileVer400 && _version < kFileVer500 && member->_children.size() == 1) {
-		paletteId = member->_children[0].index;
-	} else if (_version < kFileVer400) {
-		// For D3 and below, palette IDs are stored in the CLUT resource as cast ID + 1024
-		paletteId = key + _castIDoffset;
-	} else {
-		warning("Cast::loadPaletteData(): Expected 1 child for palette cast, got %d", member->_children.size());
-	}
-	if (paletteId) {
-		debugC(2, kDebugImages, "Cast::loadPaletteData(): linking palette id %d to cast index %d", paletteId, key);
-		member->_palette = g_director->getPalette(paletteId);
-	}
-}
-
-void Cast::loadFilmLoopData(int key, FilmLoopCastMember *member) {
-	if (_version < kFileVer400) {
-		// Director 3 and below should have a SCVW resource
-		uint16 filmLoopId = key + _castIDoffset;
-		uint32 tag = MKTAG('S', 'C', 'V', 'W');
-		Common::SeekableReadStreamEndian *loop = _castArchive->getResource(tag, filmLoopId);
-		debugC(2, kDebugLoading, "****** Loading '%s' id: %d, %d bytes", tag2str(tag), filmLoopId, (int)loop->size());
-		member->loadFilmLoopData(*loop);
-		delete loop;
-	} else if (_version >= kFileVer400 && _version < kFileVer500) {
-		if (member->_children.size() == 1) {
-			uint16 filmLoopId = member->_children[0].index;
-			uint32 tag = member->_children[0].tag;
-			if (_castArchive->hasResource(tag, filmLoopId)) {
-				Common::SeekableReadStreamEndian *loop = _castArchive->getResource(tag, filmLoopId);
-				debugC(2, kDebugLoading, "****** Loading '%s' id: %d, %d bytes", tag2str(tag), filmLoopId, (int)loop->size());
-				member->loadFilmLoopDataV4(*loop);
-				delete loop;
-			} else {
-				warning("Cast::loadFilmLoopData(): Film loop not found");
-			}
-		} else {
-			warning("Cast::loadFilmLoopData(): Expected 1 child for film loop cast, got %d", member->_children.size());
-		}
-	} else {
-		warning("STUB: Cast::loadFilmLoopData(): Film loops not supported for version %d", _version);
-	}
-}
-
-void Cast::loadBitmapData(int key, BitmapCastMember *bitmapCast) {
-	uint32 tag = bitmapCast->_tag;
-	uint16 imgId = key;
-	uint16 realId = 0;
-
-	Image::ImageDecoder *img = nullptr;
-	Common::SeekableReadStream *pic = nullptr;
-
-	if (_version >= kFileVer400) {
-		if (bitmapCast->_children.size() > 0) {
-			imgId = bitmapCast->_children[0].index;
-			tag = bitmapCast->_children[0].tag;
-
-			if (_castArchive->hasResource(tag, imgId))
-				pic = _castArchive->getResource(tag, imgId);
-		}
-
-		if ((pic == nullptr || pic->size() == 0)
-				&& _castsInfo.contains(key) && !_castsInfo[key]->fileName.empty()) {
-			// image file is linked, load from the filesystem
-			Common::String filename = _castsInfo[key]->fileName;
-			Common::String directory = _castsInfo[key]->directory;
-
-			Common::String imageFilename = directory + g_director->_dirSeparator + filename;
-
-			Common::Path path = Common::Path(pathMakeRelative(imageFilename), g_director->_dirSeparator);
-
-			Common::SeekableReadStream *file = Common::MacResManager::openFileOrDataFork(path);
-			if (file) {
-				// Detect the filetype. Director will ignore file extensions, as do we.
-				Image::ImageDecoder *decoder = nullptr;
-				uint32 fileType = file->readUint32BE();
-				file->seek(0);
-
-				if ((fileType >> 16) == MKTAG16('B', 'M')) {
-					// Windows Bitmap file
-					decoder = new Image::BitmapDecoder();
-				} else if ((fileType == 0xffd8ffe0) || (fileType == 0xffd8ffe1) || (fileType == 0xffd8ffe2)) {
-					// JPEG file
-					decoder = new Image::JPEGDecoder();
-				} else {
-					// Well... Director allowed someone to add it, so it must be a PICT. No further questions!
-					decoder = new Image::PICTDecoder();
-				}
-
-				bool res = decoder->loadStream(*file);
-				delete file;
-
-				if (res) {
-					bitmapCast->setPicture(*decoder, decoder->hasPalette());
-					bitmapCast->_external = true;
-
-					const Graphics::Surface *surf = decoder->getSurface();
-					if (decoder->hasPalette()) {
-						// For BMPs this sometimes gets set to 16 in the cast record,
-						// we should go with what the target image has.
-						bitmapCast->_bitsPerPixel = 8;
-					}
-
-					debugC(5, kDebugImages, "Cast::loadBitmapData(): Bitmap: id: %d, w: %d, h: %d, flags1: %x, flags2: %x bytes: %x, bpp: %d clut: %x", imgId, surf->w, surf->h, bitmapCast->_flags1, bitmapCast->_flags2, bitmapCast->_bytes, bitmapCast->_bitsPerPixel, bitmapCast->_clut);
-					delete pic;
-					delete decoder;
-					return;
-				} else {
-					delete decoder;
-					warning("BUILDBOT: Cast::loadBitmapData(): wrong format for external picture '%s'", path.toString().c_str());
-				}
-			} else {
-				warning("Cast::loadBitmapData(): cannot open external picture '%s'", path.toString().c_str());
-			}
-		}
-	} else {
-		if (_loadedCast->contains(imgId)) {
-			bitmapCast->_tag = tag = ((BitmapCastMember *)_loadedCast->getVal(imgId))->_tag;
-			realId = imgId + _castIDoffset;
-			pic = _castArchive->getResource(tag, realId);
-		}
-	}
-
-	if (pic == nullptr) {
-		warning("Cast::loadBitmapData(): Bitmap image %d not found", imgId);
-		return;
-	}
-
-	int w = bitmapCast->_initialRect.width();
-	int h = bitmapCast->_initialRect.height();
-
-	switch (tag) {
-	case MKTAG('D', 'I', 'B', ' '):
-		debugC(2, kDebugLoading, "****** Loading 'DIB ' id: %d (%d), %d bytes", imgId, realId, (int)pic->size());
-		img = new DIBDecoder();
-		break;
-
-	case MKTAG('B', 'I', 'T', 'D'):
-		debugC(2, kDebugLoading, "****** Loading 'BITD' id: %d (%d), %d bytes", imgId, realId, (int)pic->size());
-
-		if (w > 0 && h > 0) {
-			if (_version < kFileVer600) {
-				img = new BITDDecoder(w, h, bitmapCast->_bitsPerPixel, bitmapCast->_pitch, _vm->getPalette(), _version);
-			} else {
-				img = new Image::BitmapDecoder();
-			}
-		} else {
-			warning("Cast::loadBitmapData(): Bitmap image %d not found", imgId);
-		}
-
-		break;
-
-	default:
-		warning("Cast::loadBitmapData(): Unknown Bitmap CastMember Tag: [%d] %s", tag, tag2str(tag));
-		break;
-	}
-
-	if (!img || !img->loadStream(*pic)) {
-		warning("Cast::loadBitmapData(): Unable to load id: %d", imgId);
-		delete pic;
-		delete img;
-		return;
-	}
-
-	bitmapCast->setPicture(*img, true);
-
-	delete img;
-	delete pic;
-
-	debugC(5, kDebugImages, "Cast::loadBitmapData(): Bitmap: id: %d, w: %d, h: %d, flags1: %x, flags2: %x bytes: %x, bpp: %d clut: %x", imgId, w, h, bitmapCast->_flags1, bitmapCast->_flags2, bitmapCast->_bytes, bitmapCast->_bitsPerPixel, bitmapCast->_clut);
-}
-
-void Cast::loadSoundData(int key, SoundCastMember *soundCast) {
-	uint32 tag = MKTAG('S', 'N', 'D', ' ');
-	uint16 sndId = (uint16)(key + _castIDoffset);
-
-	if (_version >= kFileVer400 && soundCast->_children.size() > 0) {
-		sndId = soundCast->_children[0].index;
-		tag = soundCast->_children[0].tag;
-	}
-
-	Common::SeekableReadStreamEndian *sndData = nullptr;
-
-	if (!_castArchive->hasResource(tag, sndId)) {
-		if (_castArchive->hasResource(MKTAG('s', 'n', 'd', ' '), sndId))
-			tag = MKTAG('s', 'n', 'd', ' ');
-	}
-
-	if (_castArchive->hasResource(tag, sndId)) {
-		debugC(2, kDebugLoading, "****** Loading '%s' id: %d", tag2str(tag), sndId);
-		sndData = _castArchive->getResource(tag, sndId);
-	}
-
-	if (sndData == nullptr || sndData->size() == 0) {
-		// audio file is linked, load from the filesystem
-		Common::String filename = _castsInfo[key]->fileName;
-
-		if (!_castsInfo[key]->directory.empty())
-			filename = _castsInfo[key]->directory + g_director->_dirSeparator + _castsInfo[key]->fileName;
-
-		AudioFileDecoder *audio = new AudioFileDecoder(filename);
-		soundCast->_audio = audio;
-	} else {
-		SNDDecoder *audio = new SNDDecoder();
-		audio->loadStream(*sndData);
-		soundCast->_audio = audio;
-		soundCast->_size = sndData->size();
-		if (_version < kFileVer400) {
-			// The looping flag wasn't added to sound cast members until D4.
-			// In older versions, always loop sounds that contain a loop start and end.
-			soundCast->_looping = audio->hasLoopBounds();
-		}
-	}
-	delete sndData;
-}
-
 void Cast::loadCastMemberData() {
 	debugC(1, kDebugLoading, "****** Loading casts data: sprite palettes, images, filmloops, sounds and texts.");
 
@@ -867,26 +630,7 @@ void Cast::loadCastMemberData() {
 	for (Common::HashMap<int, CastMember *>::iterator c = _loadedCast->begin(); c != _loadedCast->end(); ++c) {
 		if (!c->_value)
 			continue;
-
-		switch (c->_value->_type){
-			case kCastPalette:
-				loadPaletteData(c->_key, (PaletteCastMember *)c->_value);
-				break;
-			case kCastFilmLoop:
-				loadFilmLoopData(c->_key, (FilmLoopCastMember *)c->_value);
-				break;
-			case kCastBitmap:
-				loadBitmapData(c->_key, (BitmapCastMember *)c->_value);
-				break;
-			case kCastSound:
-				loadSoundData(c->_key, (SoundCastMember *)c->_value);
-				break;
-			case kCastText:
-			case kCastButton:
-				loadStxtData(c->_key, (TextCastMember *)c->_value);
-			default:
-				break;
-		}
+		c->_value->load();
 
 		if (debugChannelSet(-1, kDebugFewFramesOnly) && idx++ > 0 && !(idx % 200))
 			debug("Loaded %d casts data", idx);
@@ -936,6 +680,13 @@ Common::String Cast::getVideoPath(int castId) {
 	return res;
 }
 
+Common::SeekableReadStreamEndian *Cast::getResource(uint32 tag, uint16 id) {
+	if (!_castArchive || !_castArchive->hasResource(tag, id))
+		return nullptr;
+
+	return _castArchive->getResource(tag, id);
+}
+
 PaletteV4 Cast::loadPalette(Common::SeekableReadStreamEndian &stream) {
 	int size = stream.size();
 	debugC(3, kDebugLoading, "Cast::loadPalette(): %d bytes", size);
diff --git a/engines/director/cast.h b/engines/director/cast.h
index 7de16e2b8fb..29b64c7a50c 100644
--- a/engines/director/cast.h
+++ b/engines/director/cast.h
@@ -96,11 +96,6 @@ public:
 	void loadSord(Common::SeekableReadStreamEndian &stream);
 
 	void loadCastMemberData();
-	void loadStxtData(int key, TextCastMember *member);
-	void loadPaletteData(int key, PaletteCastMember *member);
-	void loadFilmLoopData(int key, FilmLoopCastMember *member);
-	void loadBitmapData(int key, BitmapCastMember *bitmapCast);
-	void loadSoundData(int key, SoundCastMember *soundCast);
 
 	int getCastSize();
 	Common::Rect getCastMemberInitialRect(int castId);
@@ -113,6 +108,7 @@ public:
 	CastMemberInfo *getCastMemberInfo(int castId);
 	const Stxt *getStxt(int castId);
 	Common::String getVideoPath(int castId);
+	Common::SeekableReadStreamEndian *getResource(uint32 tag, uint16 id);
 
 	// release all castmember's widget, should be called when we are changing movie.
 	// because widget is handled by channel, thus we should clear all of those run-time info when we are switching the movie. (because we will create new widgets for cast)
diff --git a/engines/director/castmember/bitmap.cpp b/engines/director/castmember/bitmap.cpp
index 2bae5705fd6..2708f75a9e0 100644
--- a/engines/director/castmember/bitmap.cpp
+++ b/engines/director/castmember/bitmap.cpp
@@ -19,12 +19,17 @@
  *
  */
 
+#include "common/macresman.h"
 #include "graphics/surface.h"
 #include "graphics/macgui/macwidget.h"
+#include "image/bmp.h"
 #include "image/image_decoder.h"
+#include "image/jpeg.h"
+#include "image/pict.h"
 
 #include "director/director.h"
 #include "director/cast.h"
+#include "director/images.h"
 #include "director/movie.h"
 #include "director/picture.h"
 #include "director/score.h"
@@ -478,6 +483,151 @@ Common::String BitmapCastMember::formatInfo() {
 	);
 }
 
+void BitmapCastMember::load() {
+	if (_loaded)
+		return;
+
+	uint32 tag = _tag;
+	uint16 imgId = _castId;
+	uint16 realId = 0;
+
+	Image::ImageDecoder *img = nullptr;
+	Common::SeekableReadStream *pic = nullptr;
+
+	if (_cast->_version >= kFileVer400) {
+		if (_children.size() > 0) {
+			imgId = _children[0].index;
+			tag = _children[0].tag;
+
+			pic = _cast->getResource(tag, imgId);
+		}
+
+		CastMemberInfo *ci = _cast->getCastMemberInfo(_castId);
+
+		if ((pic == nullptr || pic->size() == 0)
+				&& ci && !ci->fileName.empty()) {
+			// image file is linked, load from the filesystem
+			Common::String filename = ci->fileName;
+			Common::String directory = ci->directory;
+
+			Common::String imageFilename = directory + g_director->_dirSeparator + filename;
+
+			Common::Path path = Common::Path(pathMakeRelative(imageFilename), g_director->_dirSeparator);
+
+			Common::SeekableReadStream *file = Common::MacResManager::openFileOrDataFork(path);
+			if (file) {
+				// Detect the filetype. Director will ignore file extensions, as do we.
+				Image::ImageDecoder *decoder = nullptr;
+				uint32 fileType = file->readUint32BE();
+				file->seek(0);
+
+				if ((fileType >> 16) == MKTAG16('B', 'M')) {
+					// Windows Bitmap file
+					decoder = new Image::BitmapDecoder();
+				} else if ((fileType == 0xffd8ffe0) || (fileType == 0xffd8ffe1) || (fileType == 0xffd8ffe2)) {
+					// JPEG file
+					decoder = new Image::JPEGDecoder();
+				} else {
+					// Well... Director allowed someone to add it, so it must be a PICT. No further questions!
+					decoder = new Image::PICTDecoder();
+				}
+
+				bool res = decoder->loadStream(*file);
+				delete file;
+
+				if (res) {
+					setPicture(*decoder, decoder->hasPalette());
+					_external = true;
+
+					const Graphics::Surface *surf = decoder->getSurface();
+					if (decoder->hasPalette()) {
+						// For BMPs this sometimes gets set to 16 in the cast record,
+						// we should go with what the target image has.
+						_bitsPerPixel = 8;
+					}
+
+					debugC(5, kDebugImages, "BitmapCastMember::load(): Bitmap: id: %d, w: %d, h: %d, flags1: %x, flags2: %x bytes: %x, bpp: %d clut: %x", imgId, surf->w, surf->h, _flags1, _flags2, _bytes, _bitsPerPixel, _clut);
+					delete pic;
+					delete decoder;
+					_loaded = true;
+					return;
+				} else {
+					delete decoder;
+					warning("BUILDBOT: BitmapCastMember::load(): wrong format for external picture '%s'", path.toString().c_str());
+				}
+			} else {
+				warning("BitmapCastMember::load(): cannot open external picture '%s'", path.toString().c_str());
+			}
+		}
+	} else {
+		realId = imgId + _cast->_castIDoffset;
+		pic = _cast->getResource(tag, realId);
+	}
+
+	if (pic == nullptr) {
+		warning("BitmapCastMember::load(): Bitmap image %d not found", imgId);
+		return;
+	}
+
+	int w = _initialRect.width();
+	int h = _initialRect.height();
+
+	switch (tag) {
+	case MKTAG('D', 'I', 'B', ' '):
+		debugC(2, kDebugLoading, "****** Loading 'DIB ' id: %d (%d), %d bytes", imgId, realId, (int)pic->size());
+		img = new DIBDecoder();
+		break;
+
+	case MKTAG('B', 'I', 'T', 'D'):
+		debugC(2, kDebugLoading, "****** Loading 'BITD' id: %d (%d), %d bytes", imgId, realId, (int)pic->size());
+
+		if (w > 0 && h > 0) {
+			if (_cast->_version < kFileVer600) {
+				img = new BITDDecoder(w, h, _bitsPerPixel, _pitch, g_director->getPalette(), _cast->_version);
+			} else {
+				img = new Image::BitmapDecoder();
+			}
+		} else {
+			warning("BitmapCastMember::load(): Bitmap image %d not found", imgId);
+		}
+
+		break;
+
+	default:
+		warning("BitmapCastMember::load(): Unknown Bitmap CastMember Tag: [%d] %s", tag, tag2str(tag));
+		break;
+	}
+
+	if (!img || !img->loadStream(*pic)) {
+		warning("BitmapCastMember::load(): Unable to load id: %d", imgId);
+		delete pic;
+		delete img;
+		return;
+	}
+
+	setPicture(*img, true);
+
+	delete img;
+	delete pic;
+
+	debugC(5, kDebugImages, "BitmapCastMember::load(): Bitmap: id: %d, w: %d, h: %d, flags1: %x, flags2: %x bytes: %x, bpp: %d clut: %x", imgId, w, h, _flags1, _flags2, _bytes, _bitsPerPixel, _clut);
+
+	_loaded = true;
+}
+
+void BitmapCastMember::unload() {
+	if (!_loaded)
+		return;
+
+	delete _picture;
+	_picture = nullptr;
+
+	delete _ditheredImg;
+	_ditheredImg = nullptr;
+
+	_loaded = false;
+}
+
 PictureReference *BitmapCastMember::getPicture() const {
 	auto picture = new PictureReference;
 
diff --git a/engines/director/castmember/bitmap.h b/engines/director/castmember/bitmap.h
index ae8808983fe..bdacde3f14a 100644
--- a/engines/director/castmember/bitmap.h
+++ b/engines/director/castmember/bitmap.h
@@ -48,6 +48,9 @@ public:
 
 	Common::String formatInfo() override;
 
+	void load() override;
+	void unload() override;
+
 	PictureReference *getPicture() const;
 	void setPicture(PictureReference &picture);
 	void setPicture(Image::ImageDecoder &image, bool adjustSize);
diff --git a/engines/director/castmember/castmember.cpp b/engines/director/castmember/castmember.cpp
index 0c7692c8bf6..2e1c4c29f9d 100644
--- a/engines/director/castmember/castmember.cpp
+++ b/engines/director/castmember/castmember.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "common/events.h"
 #include "director/director.h"
 #include "director/cast.h"
 #include "director/castmember/castmember.h"
@@ -36,6 +37,7 @@ CastMember::CastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndi
 	_size = stream.size();
 	_flags1 = 0;
 
+	_loaded = false;
 	_modified = true;
 	_isChanged = false;
 
@@ -54,6 +56,7 @@ CastMember::CastMember(Cast *cast, uint16 castId) : Object<CastMember>("CastMemb
 	_size = 0;
 	_flags1 = 0;
 
+	_loaded = false;
 	_modified = true;
 	_isChanged = false;
 
@@ -238,4 +241,18 @@ CastMemberInfo *CastMember::getInfo() {
 	return _cast->getCastMemberInfo(_castId);
 }
 
+void CastMember::load() {
+	if (_loaded)
+		return;
+
+	_loaded = true;
+}
+
+void CastMember::unload() {
+	if (!_loaded)
+		return;
+
+	_loaded = false;
+}
+
 } // End of namespace Director
diff --git a/engines/director/castmember/castmember.h b/engines/director/castmember/castmember.h
index f5e8ec22948..1ac3a103b67 100644
--- a/engines/director/castmember/castmember.h
+++ b/engines/director/castmember/castmember.h
@@ -55,6 +55,10 @@ public:
 	uint16 getID() { return _castId; }
 	CastMemberInfo *getInfo();
 
+	virtual void load();
+	virtual void unload();
+	bool isLoaded() { return _loaded; }
+
 	virtual bool isEditable() { return false; }
 	virtual void setEditable(bool editable) {}
 	virtual bool isModified() { return _modified; }
@@ -98,6 +102,7 @@ protected:
 	uint16 _castId;
 	// a link to the widget we created, we may use it later
 	Graphics::MacWidget *_widget;
+	bool _loaded;
 	bool _modified;
 	bool _isChanged;
 };
diff --git a/engines/director/castmember/filmloop.cpp b/engines/director/castmember/filmloop.cpp
index 5a654eebda9..c860f2aa984 100644
--- a/engines/director/castmember/filmloop.cpp
+++ b/engines/director/castmember/filmloop.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "common/stream.h"
 #include "graphics/surface.h"
 #include "graphics/macgui/macwidget.h"
 
@@ -389,4 +390,46 @@ Common::String FilmLoopCastMember::formatInfo() {
 	);
 }
 
+void FilmLoopCastMember::load() {
+	if (_loaded)
+		return;
+
+	if (_cast->_version < kFileVer400) {
+		// Director 3 and below should have a SCVW resource
+		uint16 filmLoopId = _castId + _cast->_castIDoffset;
+		uint32 tag = MKTAG('S', 'C', 'V', 'W');
+		Common::SeekableReadStreamEndian *loop = _cast->getResource(tag, filmLoopId);
+		if (loop) {
+			debugC(2, kDebugLoading, "****** Loading '%s' id: %d, %d bytes", tag2str(tag), filmLoopId, (int)loop->size());
+			loadFilmLoopData(*loop);
+			delete loop;
+		} else {
+			warning("FilmLoopCastMember::load(): Film loop not found");
+		}
+	} else if (_cast->_version >= kFileVer400 && _cast->_version < kFileVer500) {
+		if (_children.size() == 1) {
+			uint16 filmLoopId = _children[0].index;
+			uint32 tag = _children[0].tag;
+			Common::SeekableReadStreamEndian *loop = _cast->getResource(tag, filmLoopId);
+			if (loop) {
+				debugC(2, kDebugLoading, "****** Loading '%s' id: %d, %d bytes", tag2str(tag), filmLoopId, (int)loop->size());
+				loadFilmLoopDataV4(*loop);
+				delete loop;
+			} else {
+				warning("FilmLoopCastMember::load(): Film loop not found");
+			}
+		} else {
+			warning("FilmLoopCastMember::load(): Expected 1 child for film loop cast, got %d", _children.size());
+		}
+	} else {
+		warning("STUB: FilmLoopCastMember::load(): Film loops not supported for version %d", _cast->_version);
+	}
+
+	_loaded = true;
+}
+
+void FilmLoopCastMember::unload() {
+	// No unload necessary.
+}
+
 } // End of namespace Director
diff --git a/engines/director/castmember/filmloop.h b/engines/director/castmember/filmloop.h
index 297eb2b2775..1085990dc15 100644
--- a/engines/director/castmember/filmloop.h
+++ b/engines/director/castmember/filmloop.h
@@ -47,6 +47,9 @@ public:
 
 	Common::String formatInfo() override;
 
+	void load() override;
+	void unload() override;
+
 	bool _enableSound;
 	bool _looping;
 	bool _crop;
diff --git a/engines/director/castmember/palette.cpp b/engines/director/castmember/palette.cpp
index d15a90f84f3..15d2866b223 100644
--- a/engines/director/castmember/palette.cpp
+++ b/engines/director/castmember/palette.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "director/director.h"
+#include "director/cast.h"
 #include "director/castmember/palette.h"
 
 namespace Director {
@@ -41,4 +42,30 @@ Common::String PaletteCastMember::formatInfo() {
 	return result;
 }
 
+void PaletteCastMember::load() {
+	if (_loaded)
+		return;
+
+	// TODO: Verify how palettes work in >D4 versions
+	int paletteId = 0;
+	if (_cast->_version >= kFileVer400 && _cast->_version < kFileVer500 && _children.size() == 1) {
+		paletteId = _children[0].index;
+	} else if (_cast->_version < kFileVer400) {
+		// For D3 and below, palette IDs are stored in the CLUT resource as cast ID + 1024
+		paletteId = _castId + _cast->_castIDoffset;
+	} else {
+		warning("PaletteCastMember::load(): Expected 1 child for palette cast, got %d", _children.size());
+	}
+	if (paletteId) {
+		debugC(2, kDebugImages, "PaletteCastMember::load(): linking palette id %d to cast index %d", paletteId, _castId);
+		_palette = g_director->getPalette(paletteId);
+	}
+
+	_loaded = true;
+}
+
+void PaletteCastMember::unload() {
+	// No unload necessary.
+}
+
 }
diff --git a/engines/director/castmember/palette.h b/engines/director/castmember/palette.h
index 769bf9817db..84a4f25af7b 100644
--- a/engines/director/castmember/palette.h
+++ b/engines/director/castmember/palette.h
@@ -34,6 +34,9 @@ public:
 
 	Common::String formatInfo() override;
 
+	void load() override;
+	void unload() override;
+
 	PaletteV4 *_palette;
 };
 
diff --git a/engines/director/castmember/sound.cpp b/engines/director/castmember/sound.cpp
index 4d62b1be855..838c3851545 100644
--- a/engines/director/castmember/sound.cpp
+++ b/engines/director/castmember/sound.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "director/director.h"
+#include "director/cast.h"
 #include "director/sound.h"
 #include "director/castmember/sound.h"
 
@@ -43,4 +44,65 @@ Common::String SoundCastMember::formatInfo() {
 	);
 }
 
+void SoundCastMember::load() {
+	if (_loaded)
+		return;
+
+	uint32 tag = MKTAG('S', 'N', 'D', ' ');
+	uint16 sndId = (uint16)(_castId + _cast->_castIDoffset);
+
+	if (_cast->_version >= kFileVer400 && _children.size() > 0) {
+		sndId = _children[0].index;
+		tag = _children[0].tag;
+	}
+
+	Common::SeekableReadStreamEndian *sndData = _cast->getResource(tag, sndId);
+	if (!sndData) {
+		tag = MKTAG('s', 'n', 'd', ' ');
+		sndData = _cast->getResource(tag, sndId);
+	}
+
+	if (sndData == nullptr || sndData->size() == 0) {
+		// audio file is linked, load from the filesystem
+		CastMemberInfo *ci = _cast->getCastMemberInfo(_castId);
+		if (ci) {
+			Common::String filename = ci->fileName;
+
+			if (!ci->directory.empty())
+				filename = ci->directory + g_director->_dirSeparator + ci->fileName;
+
+			AudioFileDecoder *audio = new AudioFileDecoder(filename);
+			_audio = audio;
+		} else {
+			warning("Sound::load(): no resource or info found for cast member %d, skipping", _castId);
+		}
+	} else {
+		SNDDecoder *audio = new SNDDecoder();
+		audio->loadStream(*sndData);
+		_audio = audio;
+		_size = sndData->size();
+		if (_cast->_version < kFileVer400) {
+			// The looping flag wasn't added to sound cast members until D4.
+			// In older versions, always loop sounds that contain a loop start and end.
+			_looping = audio->hasLoopBounds();
+		}
+	}
+	if (sndData)
+		delete sndData;
+
+	_loaded = true;
+}
+
+void SoundCastMember::unload() {
+	if (!_loaded)
+		return;
+
+	delete _audio;
+	_audio = nullptr;
+	_size = 0;
+	_looping = false;
+
+	_loaded = false;
+}
+
 } // End of namespace Director
diff --git a/engines/director/castmember/sound.h b/engines/director/castmember/sound.h
index 57e9372fef7..f4b7527b493 100644
--- a/engines/director/castmember/sound.h
+++ b/engines/director/castmember/sound.h
@@ -33,6 +33,8 @@ public:
 	SoundCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version);
 	~SoundCastMember();
 
+	void load() override;
+	void unload() override;
 	Common::String formatInfo() override;
 
 	bool _looping;
diff --git a/engines/director/castmember/text.cpp b/engines/director/castmember/text.cpp
index 1f26b8b6543..eb83e61c162 100644
--- a/engines/director/castmember/text.cpp
+++ b/engines/director/castmember/text.cpp
@@ -23,6 +23,7 @@
 #include "graphics/macgui/mactext.h"
 
 #include "director/director.h"
+#include "director/cast.h"
 #include "director/channel.h"
 #include "director/movie.h"
 #include "director/score.h"
@@ -366,7 +367,31 @@ Common::String TextCastMember::formatInfo() {
 		getForeColor(), getBackColor(),
 		_editable, format.c_str()
 	);
+}
+
+void TextCastMember::load() {
+	if (_loaded)
+		return;
+
+	uint stxtid;
+	if (_cast->_version >= kFileVer400 && _children.size() > 0)
+		stxtid = _children[0].index;
+	else
+		stxtid = _castId;
+
+	if (_cast->_loadedStxts->contains(stxtid)) {
+		const Stxt *stxt = _cast->_loadedStxts->getVal(stxtid);
+		importStxt(stxt);
+		_size = stxt->_size;
+	} else {
+		warning("TextCastMember::load(): stxtid %i isn't loaded", stxtid);
+	}
+
+	_loaded = true;
+}
 
+void TextCastMember::unload() {
+	// No unload necessary.
 }
 
 bool TextCastMember::hasField(int field) {
diff --git a/engines/director/castmember/text.h b/engines/director/castmember/text.h
index a579bb51e68..4db38085f69 100644
--- a/engines/director/castmember/text.h
+++ b/engines/director/castmember/text.h
@@ -58,6 +58,9 @@ public:
 
 	Common::String formatInfo() override;
 
+	void load() override;
+	void unload() override;
+
 	SizeType _borderSize;
 	SizeType _gutterSize;
 	SizeType _boxShadow;


Commit: 4557c5225813d5389bcf5fcf2733658e170c797f
    https://github.com/scummvm/scummvm/commit/4557c5225813d5389bcf5fcf2733658e170c797f
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: Lazily load cast member data from archive

Required for e.g. Inscape games, where movie files contain more data
than could (at the time) fit into RAM. Removes a noticable wait when
switching movie files.

Changed paths:
    engines/director/cast.cpp
    engines/director/cast.h
    engines/director/castmember/bitmap.cpp
    engines/director/castmember/sound.cpp
    engines/director/castmember/text.cpp


diff --git a/engines/director/cast.cpp b/engines/director/cast.cpp
index e0e6ebafcf7..26f3df8f314 100644
--- a/engines/director/cast.cpp
+++ b/engines/director/cast.cpp
@@ -58,6 +58,7 @@ Cast::Cast(Movie *movie, uint16 castLibID, bool isShared) {
 
 	_castLibID = castLibID;
 	_isShared = isShared;
+	_loadMutex = true;
 
 	_lingoArchive = new LingoArchive(this);
 
@@ -117,6 +118,13 @@ CastMember *Cast::getCastMember(int castId) {
 	if (_loadedCast && _loadedCast->contains(castId)) {
 		result = _loadedCast->getVal(castId);
 	}
+	if (result && _loadMutex) {
+		// Archives only support having one stream open at a time,
+		// prevent recursive calls to CastMember::load()
+		_loadMutex = false;
+		result->load();
+		_loadMutex = true;
+	}
 	return result;
 }
 
@@ -127,26 +135,23 @@ void Cast::releaseCastMemberWidget() {
 }
 
 CastMember *Cast::getCastMemberByNameAndType(const Common::String &name, CastType type) {
-	CastMember *result = nullptr;
-
 	if (type == kCastTypeAny) {
 		if (_castsNames.contains(name)) {
-			result = _loadedCast->getVal(_castsNames[name]);
+			return getCastMember(_castsNames[name]);
 		}
 	} else {
 		Common::String cname = Common::String::format("%s:%d", name.c_str(), type);
 
 		if (_castsNames.contains(cname))
-			result = _loadedCast->getVal(_castsNames[cname]);
+			return getCastMember(_castsNames[cname]);
 	}
-	return result;
+	return nullptr;
 }
 
 CastMember *Cast::getCastMemberByScriptId(int scriptId) {
-	CastMember *result = nullptr;
 	if (_castsScriptIds.contains(scriptId))
-		result = _loadedCast->getVal(_castsScriptIds[scriptId]);
-	return result;
+		return getCastMember(_castsScriptIds[scriptId]);
+	return nullptr;
 }
 
 CastMemberInfo *Cast::getCastMemberInfo(int castId) {
@@ -618,23 +623,6 @@ void Cast::loadCast() {
 		}
 
 	}
-
-	loadCastMemberData();
-}
-
-void Cast::loadCastMemberData() {
-	debugC(1, kDebugLoading, "****** Loading casts data: sprite palettes, images, filmloops, sounds and texts.");
-
-	int idx = 0;
-
-	for (Common::HashMap<int, CastMember *>::iterator c = _loadedCast->begin(); c != _loadedCast->end(); ++c) {
-		if (!c->_value)
-			continue;
-		c->_value->load();
-
-		if (debugChannelSet(-1, kDebugFewFramesOnly) && idx++ > 0 && !(idx % 200))
-			debug("Loaded %d casts data", idx);
-	}
 }
 
 Common::String Cast::getVideoPath(int castId) {
diff --git a/engines/director/cast.h b/engines/director/cast.h
index 29b64c7a50c..b8492132107 100644
--- a/engines/director/cast.h
+++ b/engines/director/cast.h
@@ -95,8 +95,6 @@ public:
 	void loadExternalSound(Common::SeekableReadStreamEndian &stream);
 	void loadSord(Common::SeekableReadStreamEndian &stream);
 
-	void loadCastMemberData();
-
 	int getCastSize();
 	Common::Rect getCastMemberInitialRect(int castId);
 	void setCastMemberModified(int castId);
@@ -162,6 +160,7 @@ private:
 	Movie *_movie;
 
 	bool _isShared;
+	bool _loadMutex;
 
 	Common::String _macName;
 
diff --git a/engines/director/castmember/bitmap.cpp b/engines/director/castmember/bitmap.cpp
index 2708f75a9e0..084d9cf1221 100644
--- a/engines/director/castmember/bitmap.cpp
+++ b/engines/director/castmember/bitmap.cpp
@@ -516,6 +516,7 @@ void BitmapCastMember::load() {
 
 			Common::SeekableReadStream *file = Common::MacResManager::openFileOrDataFork(path);
 			if (file) {
+				debugC(2, kDebugLoading, "****** Loading file '%s', cast id: %d", imageFilename.c_str(), imgId);
 				// Detect the filetype. Director will ignore file extensions, as do we.
 				Image::ImageDecoder *decoder = nullptr;
 				uint32 fileType = file->readUint32BE();
diff --git a/engines/director/castmember/sound.cpp b/engines/director/castmember/sound.cpp
index 838c3851545..ed6dd45e974 100644
--- a/engines/director/castmember/sound.cpp
+++ b/engines/director/castmember/sound.cpp
@@ -71,12 +71,14 @@ void SoundCastMember::load() {
 			if (!ci->directory.empty())
 				filename = ci->directory + g_director->_dirSeparator + ci->fileName;
 
+			debugC(2, kDebugLoading, "****** Loading file '%s', cast id: %d", filename.c_str(), sndId);
 			AudioFileDecoder *audio = new AudioFileDecoder(filename);
 			_audio = audio;
 		} else {
 			warning("Sound::load(): no resource or info found for cast member %d, skipping", _castId);
 		}
 	} else {
+		debugC(2, kDebugLoading, "****** Loading '%s' id: %d, %d bytes", tag2str(tag), sndId, (int)sndData->size());
 		SNDDecoder *audio = new SNDDecoder();
 		audio->loadStream(*sndData);
 		_audio = audio;
diff --git a/engines/director/castmember/text.cpp b/engines/director/castmember/text.cpp
index eb83e61c162..a5c451ded60 100644
--- a/engines/director/castmember/text.cpp
+++ b/engines/director/castmember/text.cpp
@@ -373,6 +373,9 @@ void TextCastMember::load() {
 	if (_loaded)
 		return;
 
+	if (!_cast->_loadedStxts)
+		return;
+
 	uint stxtid;
 	if (_cast->_version >= kFileVer400 && _children.size() > 0)
 		stxtid = _children[0].index;


Commit: 14af0945150d4cae2e7add77b502c70dc9be131e
    https://github.com/scummvm/scummvm/commit/14af0945150d4cae2e7add77b502c70dc9be131e
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: XOBJ: Replace stub boilerplate with macro

Changed paths:
    engines/director/detection_tables.h
    engines/director/lingo/lingo-utils.h
    engines/director/lingo/tests/equality.lingo
    engines/director/lingo/xlibs/barakeobj.cpp
    engines/director/lingo/xlibs/batqt.cpp
    engines/director/lingo/xlibs/cdromxobj.cpp
    engines/director/lingo/xlibs/darkenscreen.cpp
    engines/director/lingo/xlibs/dpwavi.cpp
    engines/director/lingo/xlibs/draw.cpp
    engines/director/lingo/xlibs/ednox.cpp
    engines/director/lingo/xlibs/fileio.cpp
    engines/director/lingo/xlibs/fplayxobj.cpp
    engines/director/lingo/xlibs/gpid.cpp
    engines/director/lingo/xlibs/gpid.h
    engines/director/lingo/xlibs/jitdraw3.cpp
    engines/director/lingo/xlibs/jwxini.cpp
    engines/director/lingo/xlibs/moovxobj.cpp
    engines/director/lingo/xlibs/orthoplayxobj.cpp
    engines/director/lingo/xlibs/popupmenuxobj.cpp
    engines/director/lingo/xlibs/printomatic.cpp
    engines/director/lingo/xlibs/qtmovie.cpp
    engines/director/lingo/xlibs/serialportxobj.cpp
    engines/director/lingo/xlibs/soundjam.cpp
    engines/director/lingo/xlibs/spacemgr.cpp
    engines/director/lingo/xlibs/valkyrie.cpp
    engines/director/lingo/xlibs/videodiscxobj.cpp
    engines/director/lingo/xlibs/widgetxobj.cpp
    engines/director/lingo/xlibs/xio.cpp


diff --git a/engines/director/detection_tables.h b/engines/director/detection_tables.h
index 024faf0a7e9..1d4611afab3 100644
--- a/engines/director/detection_tables.h
+++ b/engines/director/detection_tables.h
@@ -5300,7 +5300,7 @@ static const DirectorGameDescription gameDescriptions[] = {
 	WINGAME1("trekguideds9", "v1.0", "OMNI_DS9.EXE", "0058390ff77e527c7bb413081004c304", 805517, 400),
 
 	MACGAME1("trekguidetng", "v1.1", "TNG Episodes", "1c2e5371b835680e7c1ca8bcea008bef", 520149, 400),
-	WINGAME1("trekguidetng", "v1.1", "OMNI_TNG.EXE", "0049d72e2d5869408fc33860ec4b5c1f", 794201, 400),
+	WINGAME1("trekguidetng", "v1.1", "OMNI2/OMNI_TNG.EXE", "0049d72e2d5869408fc33860ec4b5c1f", 794201, 400),
 
 	MACGAME1("trekomni", "v1.00", "Omnipedia", "b7e69c37b7355022d400c14aa97c5d54", 516791, 400),
 	MACGAME1("trekomni", "v1.1.2 Upgrade", "Omnipedia 2", "b7e69c37b7355022d400c14aa97c5d54", 613253, 400),
diff --git a/engines/director/lingo/lingo-utils.h b/engines/director/lingo/lingo-utils.h
index 195d4118be9..567a84624f3 100644
--- a/engines/director/lingo/lingo-utils.h
+++ b/engines/director/lingo/lingo-utils.h
@@ -53,4 +53,24 @@
 		return; \
 	}
 
+#define XOBJSTUB(methname,retval) \
+	void methname(int nargs) { \
+		g_lingo->printSTUBWithArglist(#methname, nargs); \
+		g_lingo->dropStack(nargs); \
+		g_lingo->push(Datum(retval)); \
+	}
+
+#define XOBJSTUBV(methname) \
+	void methname(int nargs) { \
+		g_lingo->printSTUBWithArglist(#methname, nargs); \
+		g_lingo->dropStack(nargs); \
+		g_lingo->push(Datum()); \
+	}
+
+#define XOBJSTUBNR(methname) \
+	void methname(int nargs) { \
+		g_lingo->printSTUBWithArglist(#methname, nargs); \
+		g_lingo->dropStack(nargs); \
+	}
+
 #endif
diff --git a/engines/director/lingo/tests/equality.lingo b/engines/director/lingo/tests/equality.lingo
index 06f39ebf4c9..60c4b45dafe 100644
--- a/engines/director/lingo/tests/equality.lingo
+++ b/engines/director/lingo/tests/equality.lingo
@@ -25,7 +25,6 @@ set a to the picture of cast 1
 scummvmAssert(a <> a)
 scummvmAssert(a <> the picture of cast 1)   -- always false
 
-
 -- String comparison
 scummvmAssert("a" > "A")
 scummvmAssert("a" <= "Z")
@@ -34,8 +33,8 @@ scummvmAssert("z" > "Z")
 scummvmAssert("abba" > "Abba")
 
 -- This behaviour was fixed by 8.5
--- set save to the scummvmVersion
--- set the scummvmVersion to 850
--- scummvmAssert("a" <= "Z")
--- scummvmAssert("a" <= "A")
--- set the scummvmVersion to save
+set save to the scummvmVersion
+set the scummvmVersion to 850
+scummvmAssert("a" <= "Z")
+scummvmAssert("a" <= "A")
+set the scummvmVersion to save
diff --git a/engines/director/lingo/xlibs/barakeobj.cpp b/engines/director/lingo/xlibs/barakeobj.cpp
index a596cea8164..e3f72cee072 100644
--- a/engines/director/lingo/xlibs/barakeobj.cpp
+++ b/engines/director/lingo/xlibs/barakeobj.cpp
@@ -37,6 +37,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/barakeobj.h"
 
 
@@ -83,14 +84,7 @@ void BarakeObj::m_new(int nargs) {
 void BarakeObj::m_clear(int nargs) {
 }
 
-void BarakeObj::m_gpal(int nargs) {
-	g_lingo->printSTUBWithArglist("BarakeObj::Gpal", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void BarakeObj::m_line(int nargs) {
-	g_lingo->printSTUBWithArglist("BarakeObj::Line", nargs);
-	g_lingo->dropStack(nargs);
-}
+XOBJSTUBNR(BarakeObj::m_gpal)
+XOBJSTUBNR(BarakeObj::m_line)
 
 } // End of namespace Director
diff --git a/engines/director/lingo/xlibs/batqt.cpp b/engines/director/lingo/xlibs/batqt.cpp
index 4e28933ffdf..5858a92e875 100644
--- a/engines/director/lingo/xlibs/batqt.cpp
+++ b/engines/director/lingo/xlibs/batqt.cpp
@@ -55,6 +55,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/batqt.h"
 
 
@@ -116,123 +117,25 @@ void BatQT::m_new(int nargs) {
 	g_lingo->push(g_lingo->_state->me);
 }
 
-void BatQT::m_dispose(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_dispose", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void BatQT::m_name(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_name", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(""));
-}
-
-void BatQT::m_status(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_status", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void BatQT::m_error(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_error", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(""));
-}
-
-void BatQT::m_lastError(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_lastError", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(""));
-}
-
-void BatQT::m_open(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_open", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void BatQT::m_play(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_play", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void BatQT::m_stop(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_stop", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void BatQT::m_getTimeRange(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_getTimeRange", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(""));
-}
-
-void BatQT::m_getMovieBox(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_getMovieBox", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum("0,0,320,240"));
-}
-
-void BatQT::m_getTime(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_getTime", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void BatQT::m_setTime(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_setTime", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(""));
-}
-
-void BatQT::m_setVolume(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_setVolume", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(""));
-}
-
-void BatQT::m_length(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_length", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void BatQT::m_setMovieBox(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_setMovieBox", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void BatQT::m_setTimeRange(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_setTimeRange", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void BatQT::m_addCallback(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_addCallback", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void BatQT::m_removeCallback(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_removeCallback", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void BatQT::m_resetCallbacks(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_resetCallbacks", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void BatQT::m_setBatch(int nargs) {
-	g_lingo->printSTUBWithArglist("BatQT::m_setBatch", nargs);
-	g_lingo->dropStack(nargs);
-}
-
+XOBJSTUBNR(BatQT::m_dispose)
+XOBJSTUB(BatQT::m_name, "")
+XOBJSTUB(BatQT::m_status, 0)
+XOBJSTUB(BatQT::m_error, "")
+XOBJSTUB(BatQT::m_lastError, "")
+XOBJSTUB(BatQT::m_open, 0)
+XOBJSTUB(BatQT::m_play, 0)
+XOBJSTUB(BatQT::m_stop, 0)
+XOBJSTUB(BatQT::m_getTimeRange, "")
+XOBJSTUB(BatQT::m_getMovieBox, "0,0,320,240")
+XOBJSTUB(BatQT::m_getTime, 0)
+XOBJSTUB(BatQT::m_setTime, "")
+XOBJSTUB(BatQT::m_setVolume, "")
+XOBJSTUB(BatQT::m_length, 0)
+XOBJSTUB(BatQT::m_setMovieBox, 0)
+XOBJSTUB(BatQT::m_setTimeRange, 0)
+XOBJSTUB(BatQT::m_addCallback, 0)
+XOBJSTUB(BatQT::m_removeCallback, 0)
+XOBJSTUB(BatQT::m_resetCallbacks, 0)
+XOBJSTUBNR(BatQT::m_setBatch)
 
 } // End of namespace Director
diff --git a/engines/director/lingo/xlibs/cdromxobj.cpp b/engines/director/lingo/xlibs/cdromxobj.cpp
index 4ab66d58d0b..93a976ec613 100644
--- a/engines/director/lingo/xlibs/cdromxobj.cpp
+++ b/engines/director/lingo/xlibs/cdromxobj.cpp
@@ -165,6 +165,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/cdromxobj.h"
 
 namespace Director {
@@ -315,11 +316,7 @@ void CDROMXObj::m_playSegment(int nargs) {
 	g_lingo->push(Datum());
 }
 
-void CDROMXObj::m_askPlay(int nargs) {
-	g_lingo->printSTUBWithArglist("CDROMXObj::m_askPlay", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
+XOBJSTUBV(CDROMXObj::m_askPlay)
 
 void CDROMXObj::m_stepFwd(int nargs) {
 	CDROMXObject *me = static_cast<CDROMXObject *>(g_lingo->_state->me.u.obj);
@@ -471,51 +468,18 @@ void CDROMXObj::m_currentTrack(int nargs) {
 	g_lingo->push(Datum(me->_cdda_status.track));
 }
 
-void CDROMXObj::m_currentTime(int nargs) {
-	g_lingo->printSTUBWithArglist("CDROMXObj::m_currentTime", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
+XOBJSTUBV(CDROMXObj::m_currentTime)
 
 // The next few methods depend on full TOC implementation, so they
 // can't be implemented right now.
-void CDROMXObj::m_firstTrack(int nargs) {
-	g_lingo->printSTUBWithArglist("CDROMXObj::m_firstTrack", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void CDROMXObj::m_lastTrack(int nargs) {
-	g_lingo->printSTUBWithArglist("CDROMXObj::m_lastTrack", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void CDROMXObj::m_totalTime(int nargs) {
-	g_lingo->printSTUBWithArglist("CDROMXObj::m_totalTime", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
+XOBJSTUBV(CDROMXObj::m_firstTrack)
+XOBJSTUBV(CDROMXObj::m_lastTrack)
+XOBJSTUBV(CDROMXObj::m_totalTime)
 
 // The scan methods depend on absolute timing, so they also require
 // a full TOC.
-void CDROMXObj::m_scanFwd(int nargs) {
-	g_lingo->printSTUBWithArglist("CDROMXObj::m_scanFwd", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void CDROMXObj::m_scanBwd(int nargs) {
-	g_lingo->printSTUBWithArglist("CDROMXObj::m_scanBwd", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void CDROMXObj::m_stopScan(int nargs) {
-	g_lingo->printSTUBWithArglist("CDROMXObj::m_stopScan", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
+XOBJSTUBV(CDROMXObj::m_scanFwd)
+XOBJSTUBV(CDROMXObj::m_scanBwd)
+XOBJSTUBV(CDROMXObj::m_stopScan)
 
 } // End of namespace Director
diff --git a/engines/director/lingo/xlibs/darkenscreen.cpp b/engines/director/lingo/xlibs/darkenscreen.cpp
index e1ff14c739e..204a794079f 100644
--- a/engines/director/lingo/xlibs/darkenscreen.cpp
+++ b/engines/director/lingo/xlibs/darkenscreen.cpp
@@ -33,6 +33,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/darkenscreen.h"
 
 
@@ -57,8 +58,6 @@ void DarkenScreen::close(int type) {
 	g_lingo->cleanupBuiltIns(builtins);
 }
 
-void DarkenScreen::m_darkenscreen(int nargs) {
-	g_lingo->printSTUBWithArglist("DarkenScreen::m_darkenscreen", nargs);
-}
+XOBJSTUBNR(DarkenScreen::m_darkenscreen)
 
 } // End of namespace Director
diff --git a/engines/director/lingo/xlibs/dpwavi.cpp b/engines/director/lingo/xlibs/dpwavi.cpp
index f67b7a76252..442da24efeb 100644
--- a/engines/director/lingo/xlibs/dpwavi.cpp
+++ b/engines/director/lingo/xlibs/dpwavi.cpp
@@ -39,6 +39,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/dpwavi.h"
 
 
diff --git a/engines/director/lingo/xlibs/draw.cpp b/engines/director/lingo/xlibs/draw.cpp
index 68c10f2c0b2..85eefb6be78 100644
--- a/engines/director/lingo/xlibs/draw.cpp
+++ b/engines/director/lingo/xlibs/draw.cpp
@@ -83,6 +83,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/draw.h"
 
 
@@ -139,84 +140,21 @@ void DrawXObj::m_new(int nargs) {
 	g_lingo->push(g_lingo->_state->me);
 }
 
-void DrawXObj::m_dispose(int nargs) {
-	g_lingo->printSTUBWithArglist("DrawXObj::m_dispose", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void DrawXObj::m_line(int nargs) {
-	g_lingo->printSTUBWithArglist("DrawXObj::m_line", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void DrawXObj::m_lineBrush(int nargs) {
-	g_lingo->printSTUBWithArglist("DrawXObj::m_lineBrush", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void DrawXObj::m_lineBrushTrans(int nargs) {
-	g_lingo->printSTUBWithArglist("DrawXObj::m_lineBrushTrans", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void DrawXObj::m_lineBrushCol(int nargs) {
-	g_lingo->printSTUBWithArglist("DrawXObj::m_lineBrushCol", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void DrawXObj::m_filterBMP(int nargs) {
-	g_lingo->printSTUBWithArglist("DrawXObj::m_filterBMP", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void DrawXObj::m_filterDIB(int nargs) {
-	g_lingo->printSTUBWithArglist("DrawXObj::m_filterDIB", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void DrawXObj::m_filterBMP128(int nargs) {
-	g_lingo->printSTUBWithArglist("DrawXObj::m_filterBMP128", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void DrawXObj::m_filterDIB128(int nargs) {
-	g_lingo->printSTUBWithArglist("DrawXObj::m_filterDIB128", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void DrawXObj::m_filterBMPMakeGhostImage(int nargs) {
-	g_lingo->printSTUBWithArglist("DrawXObj::m_filterBMPMakeGhostImage", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void DrawXObj::m_filterDIBMakeGhostImage(int nargs) {
-	g_lingo->printSTUBWithArglist("DrawXObj::m_filterDIBMakeGhostImage", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void DrawXObj::m_emptyClipboard(int nargs) {
-	g_lingo->printSTUBWithArglist("DrawXObj::m_emptyClipboard", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void DrawXObj::m_fill(int nargs) {
-	g_lingo->printSTUBWithArglist("DrawXObj::m_fill", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void DrawXObj::m_getColor(int nargs) {
-	g_lingo->printSTUBWithArglist("DrawXObj::m_getColor", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void DrawXObj::m_drawRect(int nargs) {
-	g_lingo->printSTUBWithArglist("DrawXObj::m_drawRect", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void DrawXObj::m_drawFrame(int nargs) {
-	g_lingo->printSTUBWithArglist("DrawXObj::m_drawFrame", nargs);
-	g_lingo->dropStack(nargs);
-}
+XOBJSTUB(DrawXObj::m_dispose, 0)
+XOBJSTUB(DrawXObj::m_line, 0)
+XOBJSTUB(DrawXObj::m_lineBrush, 0)
+XOBJSTUB(DrawXObj::m_lineBrushTrans, 0)
+XOBJSTUB(DrawXObj::m_lineBrushCol, 0)
+XOBJSTUB(DrawXObj::m_filterBMP, 0)
+XOBJSTUB(DrawXObj::m_filterDIB, 0)
+XOBJSTUB(DrawXObj::m_filterBMP128, 0)
+XOBJSTUB(DrawXObj::m_filterDIB128, 0)
+XOBJSTUB(DrawXObj::m_filterBMPMakeGhostImage, 0)
+XOBJSTUB(DrawXObj::m_filterDIBMakeGhostImage, 0)
+XOBJSTUB(DrawXObj::m_emptyClipboard, 0)
+XOBJSTUB(DrawXObj::m_fill, 0)
+XOBJSTUB(DrawXObj::m_getColor, 0)
+XOBJSTUB(DrawXObj::m_drawRect, 0)
+XOBJSTUB(DrawXObj::m_drawFrame, 0)
 
 } // End of namespace Director
diff --git a/engines/director/lingo/xlibs/ednox.cpp b/engines/director/lingo/xlibs/ednox.cpp
index b555c217b73..ab5544e896d 100644
--- a/engines/director/lingo/xlibs/ednox.cpp
+++ b/engines/director/lingo/xlibs/ednox.cpp
@@ -58,6 +58,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/ednox.h"
 
 
@@ -118,9 +119,7 @@ void Ednox::m_new(int nargs) {
 	g_lingo->push(g_lingo->_state->me);
 }
 
-void Ednox::m_dispose(int nargs) {
-	g_lingo->printSTUBWithArglist("Ednox::m_dispose", nargs);
-}
+XOBJSTUBNR(Ednox::m_dispose)
 
 void Ednox::m_getdocumentfile(int nargs) {
 	// Common::U32String hFile = g_lingo->pop().asString();
@@ -167,13 +166,8 @@ void Ednox::m_setdrivex(int nargs) {
 	g_lingo->push(Datum(0));
 }
 
-void Ednox::m_checksoundx(int nargs) {
-	g_lingo->printSTUBWithArglist("Ednox::m_checksoundx", nargs);
-}
-
-void Ednox::m_clearsoundx(int nargs) {
-	g_lingo->printSTUBWithArglist("Ednox::m_clearsoundx", nargs);
-}
+XOBJSTUB(Ednox::m_checksoundx, "")
+XOBJSTUB(Ednox::m_clearsoundx, "")
 
 void Ednox::m_deletedocumentfile(int nargs) {
 	// Common::U32String hFile = g_lingo->pop().asString();
@@ -182,19 +176,14 @@ void Ednox::m_deletedocumentfile(int nargs) {
 	g_lingo->dropStack(nargs);
 }
 
-void Ednox::m_disabletaskswitch(int nargs) {
-	g_lingo->printSTUBWithArglist("Ednox::m_disabletaskswitch", nargs);
-}
+XOBJSTUB(Ednox::m_enabletaskswitch, "")
+XOBJSTUB(Ednox::m_disabletaskswitch, "")
 
 void Ednox::m_drawbkgndx(int nargs) {
 	// Common::U32String hBkgnd = g_lingo->pop().asString();
 	g_lingo->printSTUBWithArglist("Ednox::m_drawbkgndx", nargs);
 }
 
-void Ednox::m_enabletaskswitch(int nargs) {
-	g_lingo->printSTUBWithArglist("Ednox::m_enabletaskswitch", nargs);
-}
-
 void Ednox::m_getdocumentname(int nargs) {
 	// Common::U32String hExt = g_lingo->pop().asString();
 	// Common::U32String hDir = g_lingo->pop().asString();
@@ -208,25 +197,15 @@ void Ednox::m_error(int nargs) {
 	g_lingo->dropStack(nargs);
 }
 
-void Ednox::m_lasterror(int nargs) {
-	g_lingo->printSTUBWithArglist("Ednox::m_lasterror", nargs);
-}
+XOBJSTUB(Ednox::m_lasterror, "")
 
 void Ednox::m_name(int nargs) {
 	g_lingo->push(Datum("ednox"));
 }
 
-void Ednox::m_status(int nargs) {
-	g_lingo->printSTUBWithArglist("Ednox::m_status", nargs);
-}
-
-void Ednox::m_playsoundx(int nargs) {
-	g_lingo->printSTUBWithArglist("Ednox::m_playsoundx", nargs);
-}
-
-void Ednox::m_restorex(int nargs) {
-	g_lingo->printSTUBWithArglist("Ednox::m_restorex", nargs);
-}
+XOBJSTUB(Ednox::m_status, 0)
+XOBJSTUB(Ednox::m_playsoundx, "")
+XOBJSTUB(Ednox::m_restorex, "")
 
 void Ednox::m_savex(int nargs) {
 	// Common::U32String hStrIn = g_lingo->pop().asString();
diff --git a/engines/director/lingo/xlibs/fileio.cpp b/engines/director/lingo/xlibs/fileio.cpp
index 1462469c9f2..51bf435642d 100644
--- a/engines/director/lingo/xlibs/fileio.cpp
+++ b/engines/director/lingo/xlibs/fileio.cpp
@@ -95,6 +95,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/fileio.h"
 
 namespace Director {
@@ -332,10 +333,7 @@ void FileIO::m_readWord(int nargs) {
 	FileIO::m_readToken(2);
 }
 
-void FileIO::m_readPict(int nargs) {
-	g_lingo->printSTUBWithArglist("FileIO::m_readPict", nargs);
-	g_lingo->push(Datum(""));
-}
+XOBJSTUB(FileIO::m_readPict, "")
 
 bool FileIO::charInMatchString(char ch, const Common::String &matchString) {
 	return matchString.contains(ch);
@@ -433,17 +431,8 @@ void FileIO::m_writeString(int nargs) {
 
 // Getters/Setters
 
-void FileIO::m_getFinderInfo(int nargs) {
-	g_lingo->printSTUBWithArglist("FileIO::m_getFinderInfo", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void FileIO::m_setFinderInfo(int nargs) {
-	g_lingo->printSTUBWithArglist("FileIO::m_setFinderInfo", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
+XOBJSTUB(FileIO::m_getFinderInfo, "")
+XOBJSTUB(FileIO::m_setFinderInfo, 0)
 
 void FileIO::m_getPosition(int nargs) {
 	FileObject *me = static_cast<FileObject *>(g_lingo->_state->me.u.obj);
@@ -515,17 +504,8 @@ void FileIO::m_fileName(int nargs) {
 	}
 }
 
-void FileIO::m_error(int nargs) {
-	g_lingo->printSTUBWithArglist("FileIO::m_error", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void FileIO::m_status(int nargs) {
-	g_lingo->printSTUBWithArglist("FileIO::m_status", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
+XOBJSTUB(FileIO::m_error, "")
+XOBJSTUB(FileIO::m_status, 0)
 
 // Other
 
diff --git a/engines/director/lingo/xlibs/fplayxobj.cpp b/engines/director/lingo/xlibs/fplayxobj.cpp
index 085994f7b45..260b9881c99 100644
--- a/engines/director/lingo/xlibs/fplayxobj.cpp
+++ b/engines/director/lingo/xlibs/fplayxobj.cpp
@@ -34,6 +34,7 @@
 #include "director/sound.h"
 #include "director/window.h"
 #include "director/lingo/lingo.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/fplayxobj.h"
 
 #include "audio/audiostream.h"
@@ -86,65 +87,16 @@ void FPlayXObj::b_fplay(int nargs) {
 	sound->playFPlaySound(arr);
 }
 
-void FPlayXObj::b_copysnd(int nargs) {
-	g_lingo->printSTUBWithArglist("FPlayXObj::b_copysnd", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void FPlayXObj::b_erasesnd(int nargs) {
-	g_lingo->printSTUBWithArglist("FPlayXObj::b_erasesnd", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void FPlayXObj::b_pastesnd(int nargs) {
-	g_lingo->printSTUBWithArglist("FPlayXObj::b_pastesnd", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void FPlayXObj::b_renamesnd(int nargs) {
-	g_lingo->printSTUBWithArglist("FPlayXObj::b_renamesnd", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void FPlayXObj::b_duplicatesnd(int nargs) {
-	g_lingo->printSTUBWithArglist("FPlayXObj::b_duplicatesnd", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void FPlayXObj::b_sndinfo(int nargs) {
-	g_lingo->printSTUBWithArglist("FPlayXObj::b_sndinfo", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void FPlayXObj::b_sndlist(int nargs) {
-	g_lingo->printSTUBWithArglist("FPlayXObj::b_sndlist", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void FPlayXObj::b_volume(int nargs) {
-	g_lingo->printSTUBWithArglist("FPlayXObj::b_volume", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void FPlayXObj::b_filename(int nargs) {
-	g_lingo->printSTUBWithArglist("FPlayXObj::b_filename", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void FPlayXObj::b_inputlevel(int nargs) {
-	g_lingo->printSTUBWithArglist("FPlayXObj::b_inputlevel", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
+XOBJSTUBV(FPlayXObj::b_copysnd)
+XOBJSTUBV(FPlayXObj::b_erasesnd)
+XOBJSTUBV(FPlayXObj::b_pastesnd)
+XOBJSTUBV(FPlayXObj::b_renamesnd)
+XOBJSTUBV(FPlayXObj::b_duplicatesnd)
+XOBJSTUBV(FPlayXObj::b_sndinfo)
+XOBJSTUBV(FPlayXObj::b_sndlist)
+XOBJSTUBV(FPlayXObj::b_volume)
+XOBJSTUBV(FPlayXObj::b_filename)
+XOBJSTUBV(FPlayXObj::b_inputlevel)
 
 void FPlayXObj::b_fsound(int nargs) {
 	if (nargs != 0) {
diff --git a/engines/director/lingo/xlibs/gpid.cpp b/engines/director/lingo/xlibs/gpid.cpp
index 55c9730d122..ad825650dc4 100644
--- a/engines/director/lingo/xlibs/gpid.cpp
+++ b/engines/director/lingo/xlibs/gpid.cpp
@@ -46,6 +46,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/gpid.h"
 
 
@@ -58,7 +59,13 @@ const char *GpidXObj::fileNames[] = {
 };
 
 static MethodProto xlibMethods[] = {
-	{ "new",   GpidXObj::m_new,					0,	0,	400 },	// D4
+	{ "new",		GpidXObj::m_new,					0,	0,	400 },	// D4
+	{ "dispose",	GpidXObj::m_dispose,				0,	0,	400 },	// D4
+	{ "name",		GpidXObj::m_name,					0,	0,	400 },	// D4
+	{ "status",		GpidXObj::m_status,					0,	0,	400 },	// D4
+	{ "error",		GpidXObj::m_error,					1,	1,	400 },	// D4
+	{ "lastError",	GpidXObj::m_lastError,				0,	0,	400 },	// D4
+	{ "getPid",		GpidXObj::m_getPid,					0,	0,	400 },	// D4
 	{ nullptr, nullptr, 0, 0, 0 }
 };
 
@@ -87,4 +94,11 @@ void GpidXObj::m_new(int nargs) {
 	g_lingo->push(g_lingo->_state->me);
 }
 
+XOBJSTUBNR(GpidXObj::m_dispose)
+XOBJSTUB(GpidXObj::m_name, "")
+XOBJSTUB(GpidXObj::m_status, 0)
+XOBJSTUB(GpidXObj::m_error, "")
+XOBJSTUB(GpidXObj::m_lastError, "")
+XOBJSTUB(GpidXObj::m_getPid, 0)
+
 } // End of namespace Director
diff --git a/engines/director/lingo/xlibs/gpid.h b/engines/director/lingo/xlibs/gpid.h
index 077309c709d..6186959fdae 100644
--- a/engines/director/lingo/xlibs/gpid.h
+++ b/engines/director/lingo/xlibs/gpid.h
@@ -39,6 +39,12 @@ void open(int type);
 void close(int type);
 
 void m_new(int nargs);
+void m_dispose(int nargs);
+void m_name(int nargs);
+void m_status(int nargs);
+void m_error(int nargs);
+void m_lastError(int nargs);
+void m_getPid(int nargs);
 
 } // End of namespace GpidXObj
 
diff --git a/engines/director/lingo/xlibs/jitdraw3.cpp b/engines/director/lingo/xlibs/jitdraw3.cpp
index 4ea7867a7aa..93494fe45b9 100644
--- a/engines/director/lingo/xlibs/jitdraw3.cpp
+++ b/engines/director/lingo/xlibs/jitdraw3.cpp
@@ -65,6 +65,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/jitdraw3.h"
 
 
@@ -113,15 +114,8 @@ void JITDraw3XObj::m_new(int nargs) {
 	g_lingo->push(g_lingo->_state->me);
 }
 
-void JITDraw3XObj::m_dispose(int nargs) {
-	g_lingo->printSTUBWithArglist("JITDraw3XObj::m_dispose", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void JITDraw3XObj::m_checkifcdrom(int nargs) {
-	g_lingo->printSTUBWithArglist("JITDraw3XObj::m_checkifcdrom", nargs);
-	g_lingo->push(Datum(0));
-}
+XOBJSTUBNR(JITDraw3XObj::m_dispose)
+XOBJSTUB(JITDraw3XObj::m_checkifcdrom, 0)
 
 void JITDraw3XObj::m_msgokcancel(int nargs) {
 	Common::U32String caption = g_lingo->pop().asString();  // Title of the message box
@@ -147,20 +141,8 @@ void JITDraw3XObj::m_msgyesno(int nargs) {
 	g_lingo->push(Datum(result == GUI::kMessageOK ? 1 : 0));
 }
 
-void JITDraw3XObj::m_gotodraw(int nargs) {
-	g_lingo->printSTUBWithArglist("JITDraw3XObj::m_gotodraw", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(3));
-}
-
-void JITDraw3XObj::m_adddrawbutton(int nargs) {
-	g_lingo->printSTUBWithArglist("JITDraw3XObj::m_adddrawbutton", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void JITDraw3XObj::m_removedrawbutton(int nargs) {
-	g_lingo->printSTUBWithArglist("JITDraw3XObj::m_removedrawbutton", nargs);
-	g_lingo->dropStack(nargs);
-}
+XOBJSTUB(JITDraw3XObj::m_gotodraw, 3)
+XOBJSTUB(JITDraw3XObj::m_adddrawbutton, 0)
+XOBJSTUB(JITDraw3XObj::m_removedrawbutton, 0)
 
 } // End of namespace Director
diff --git a/engines/director/lingo/xlibs/jwxini.cpp b/engines/director/lingo/xlibs/jwxini.cpp
index c8fdb69f49e..b83beb2c98b 100644
--- a/engines/director/lingo/xlibs/jwxini.cpp
+++ b/engines/director/lingo/xlibs/jwxini.cpp
@@ -43,6 +43,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/jwxini.h"
 
 
@@ -126,17 +127,7 @@ void JourneyWareXINIXObj::m_GetProfileString(int nargs) {
 	g_lingo->push(Datum(defaultValue)); // TODO: We only return the default for now
 }
 
-void JourneyWareXINIXObj::m_WritePrivateProfileString(int nargs) {
-	g_lingo->printSTUBWithArglist("JWXIni::WritePrivateProfileString", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0)); // TODO
-}
-
-void JourneyWareXINIXObj::m_WriteProfileString(int nargs) {
-	g_lingo->printSTUBWithArglist("JWXIni::WriteProfileString", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0)); // TODO
-}
-
+XOBJSTUB(JourneyWareXINIXObj::m_WritePrivateProfileString, 0)
+XOBJSTUB(JourneyWareXINIXObj::m_WriteProfileString, 0)
 
 } // End of namespace Director
diff --git a/engines/director/lingo/xlibs/moovxobj.cpp b/engines/director/lingo/xlibs/moovxobj.cpp
index 45e9e9155d7..4a0b7f995d6 100644
--- a/engines/director/lingo/xlibs/moovxobj.cpp
+++ b/engines/director/lingo/xlibs/moovxobj.cpp
@@ -50,6 +50,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/moovxobj.h"
 
 
@@ -118,15 +119,10 @@ void MoovXObj::m_dispose(int nargs) {
 	}
 }
 
-void MoovXObj::m_name(int nargs) {
-	// unused in C.H.A.O.S.
-	g_lingo->printSTUBWithArglist("MoovXObj::m_name", nargs);
-}
-
-void MoovXObj::m_movieInit(int nargs) {
-	// called in C.H.A.O.S. ScummVMs setup happens in playMovie
-	g_lingo->printSTUBWithArglist("MoovXObj::m_movieInit", nargs);
-}
+// unused in C.H.A.O.S.
+XOBJSTUB(MoovXObj::m_name, "MoovXObj")
+// called in C.H.A.O.S. ScummVMs setup happens in playMovie
+XOBJSTUB(MoovXObj::m_movieInit, 0)
 
 void MoovXObj::m_movieKill(int nargs) {
 	debug(5, "MoovXObj::m_movieKill");
@@ -172,21 +168,12 @@ void MoovXObj::m_playMovie(int nargs) {
 	me->_video->start();
 }
 
-void MoovXObj::m_pauseMovie(int nargs) {
-	// unused in C.H.A.O.S.
-	g_lingo->printSTUBWithArglist("MoovXObj::m_pauseMovie", nargs);
-}
-
-void MoovXObj::m_soundMovie(int nargs) {
-	// unused in C.H.A.O.S.
-	g_lingo->printSTUBWithArglist("MoovXObj::m_soundMovie", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void MoovXObj::m_stopMovie(int nargs) {
-	// unused in C.H.A.O.S.
-	g_lingo->printSTUBWithArglist("MoovXObj::m_stopMovie", nargs);
-}
+// unused in C.H.A.O.S.
+XOBJSTUB(MoovXObj::m_pauseMovie, 0)
+// unused in C.H.A.O.S.
+XOBJSTUB(MoovXObj::m_soundMovie, 0)
+// unused in C.H.A.O.S.
+XOBJSTUB(MoovXObj::m_stopMovie, 0)
 
 void MoovXObj::m_movieDone(int nargs) {
 	MoovXObject *me = static_cast<MoovXObject *>(g_lingo->_state->me.u.obj);
diff --git a/engines/director/lingo/xlibs/orthoplayxobj.cpp b/engines/director/lingo/xlibs/orthoplayxobj.cpp
index 3e2ef4e135b..ef3005653df 100644
--- a/engines/director/lingo/xlibs/orthoplayxobj.cpp
+++ b/engines/director/lingo/xlibs/orthoplayxobj.cpp
@@ -41,6 +41,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/orthoplayxobj.h"
 
 
@@ -139,7 +140,6 @@ void OrthoPlayXObj::close(int type) {
 	}
 }
 
-
 OrthoPlayXObject::OrthoPlayXObject(ObjectType ObjectType) :Object<OrthoPlayXObject>("OrthoPlayXObj") {
 	_objType = ObjectType;
 }
@@ -148,375 +148,72 @@ void OrthoPlayXObj::m_new(int nargs) {
 	g_lingo->push(g_lingo->_state->me);
 }
 
-void OrthoPlayXObj::m_setSerialPort(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_setSerialPort", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_setInitViaDlog(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_setInitViaDlog", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_getInitInfo(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_getInitInfo", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_setInitInfo(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_setInitInfo", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_getMaxDevices(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_getMaxDevices", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_getDeviceTitle(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_getDeviceTitle", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_setDevice(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_setDevice", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_selectDevice(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_selectDevice", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_getDevice(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_getDevice", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_service(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_service", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_getValue(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_getValue", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_cancel(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_cancel", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_explain(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_explain", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_idle(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_idle", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_readStatus(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_readStatus", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_readPos(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_readPos", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_searchTo(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_searchTo", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_play(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_play", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_still(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_still", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_stop(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_stop", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_scanForward(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_scanForward", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_scanReverse(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_scanReverse", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_playReverse(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_playReverse", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_fastForward(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_fastForward", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_rewind(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_rewind", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_stepForward(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_stepForward", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_stepReverse(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_stepReverse", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_shuttle(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_shuttle", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_record(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_record", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_eject(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_eject", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_prepareMedium(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_prepareMedium", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_getFirstTrack(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_getFirstTrack", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_getLastTrack(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_getLastTrack", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_getFirstFrame(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_getFirstFrame", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_getLastFrame(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_getLastFrame", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_getTrack(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_getTrack", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_resetCounter(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_resetCounter", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_audioEnable(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_audioEnable", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_audioMute(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_audioMute", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_videoEnable(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_videoEnable", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_showFrame(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_showFrame", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_getFrameResolution(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_getFrameResolution", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_setFrameResolution(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_setFrameResolution", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_hasDropFrames(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_hasDropFrames", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_sendRaw(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_sendRaw", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_readRaw(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_readRaw", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_setInPoint(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_setInPoint", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_setOutPoint(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_setOutPoint", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_setDuration(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_setDuration", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_getMinDuration(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_getMinDuration", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_setPreroll(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_setPreroll", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_getPreroll(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_getPreroll", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_setPostroll(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_setPostroll", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_getPostroll(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_getPostroll", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_setFieldDominance(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_setFieldDominance", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_playCue(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_playCue", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_playSegment(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_playSegment", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_recordCue(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_recordCue", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_recordSegment(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_recordSegment", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_recordVideoEnable(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_recordVideoEnable", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_recordAudioEnable(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_recordAudioEnable", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_assembleRecord(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_assembleRecord", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_previewRecord(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_previewRecord", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void OrthoPlayXObj::m_gotoInPoint(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_gotoInPoint", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_gotoOutPoint(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_gotoOutPoint", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_gotoPrerollPoint(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_gotoPrerollPoint", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void OrthoPlayXObj::m_gotoPostrollPoint(int nargs) {
-	g_lingo->printSTUBWithArglist("OrthoPlayXObj::m_gotoPostrollPoint", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-
+XOBJSTUBV(OrthoPlayXObj::m_setSerialPort)
+XOBJSTUBV(OrthoPlayXObj::m_setInitViaDlog)
+XOBJSTUBV(OrthoPlayXObj::m_getInitInfo)
+XOBJSTUBV(OrthoPlayXObj::m_setInitInfo)
+XOBJSTUBV(OrthoPlayXObj::m_getMaxDevices)
+XOBJSTUBV(OrthoPlayXObj::m_getDeviceTitle)
+XOBJSTUBV(OrthoPlayXObj::m_setDevice)
+XOBJSTUBV(OrthoPlayXObj::m_selectDevice)
+XOBJSTUBV(OrthoPlayXObj::m_getDevice)
+XOBJSTUBV(OrthoPlayXObj::m_service)
+XOBJSTUBV(OrthoPlayXObj::m_getValue)
+XOBJSTUBV(OrthoPlayXObj::m_cancel)
+XOBJSTUBV(OrthoPlayXObj::m_explain)
+XOBJSTUBV(OrthoPlayXObj::m_idle)
+XOBJSTUBNR(OrthoPlayXObj::m_readStatus)
+XOBJSTUBNR(OrthoPlayXObj::m_readPos)
+XOBJSTUBNR(OrthoPlayXObj::m_searchTo)
+XOBJSTUBNR(OrthoPlayXObj::m_play)
+XOBJSTUBNR(OrthoPlayXObj::m_still)
+XOBJSTUBNR(OrthoPlayXObj::m_stop)
+XOBJSTUBNR(OrthoPlayXObj::m_scanForward)
+XOBJSTUBNR(OrthoPlayXObj::m_scanReverse)
+XOBJSTUBNR(OrthoPlayXObj::m_playReverse)
+XOBJSTUBNR(OrthoPlayXObj::m_fastForward)
+XOBJSTUBNR(OrthoPlayXObj::m_rewind)
+XOBJSTUBNR(OrthoPlayXObj::m_stepForward)
+XOBJSTUBNR(OrthoPlayXObj::m_stepReverse)
+XOBJSTUBNR(OrthoPlayXObj::m_shuttle)
+XOBJSTUBNR(OrthoPlayXObj::m_record)
+XOBJSTUBNR(OrthoPlayXObj::m_eject)
+XOBJSTUBNR(OrthoPlayXObj::m_prepareMedium)
+XOBJSTUBV(OrthoPlayXObj::m_getFirstTrack)
+XOBJSTUBV(OrthoPlayXObj::m_getLastTrack)
+XOBJSTUBV(OrthoPlayXObj::m_getFirstFrame)
+XOBJSTUBV(OrthoPlayXObj::m_getLastFrame)
+XOBJSTUBV(OrthoPlayXObj::m_getTrack)
+XOBJSTUBNR(OrthoPlayXObj::m_resetCounter)
+XOBJSTUBNR(OrthoPlayXObj::m_audioEnable)
+XOBJSTUBNR(OrthoPlayXObj::m_audioMute)
+XOBJSTUBNR(OrthoPlayXObj::m_videoEnable)
+XOBJSTUBNR(OrthoPlayXObj::m_showFrame)
+XOBJSTUBV(OrthoPlayXObj::m_getFrameResolution)
+XOBJSTUBV(OrthoPlayXObj::m_setFrameResolution)
+XOBJSTUBV(OrthoPlayXObj::m_hasDropFrames)
+XOBJSTUBNR(OrthoPlayXObj::m_sendRaw)
+XOBJSTUBV(OrthoPlayXObj::m_readRaw)
+XOBJSTUBV(OrthoPlayXObj::m_setInPoint)
+XOBJSTUBV(OrthoPlayXObj::m_setOutPoint)
+XOBJSTUBV(OrthoPlayXObj::m_setDuration)
+XOBJSTUBV(OrthoPlayXObj::m_getMinDuration)
+XOBJSTUBV(OrthoPlayXObj::m_setPreroll)
+XOBJSTUBV(OrthoPlayXObj::m_getPreroll)
+XOBJSTUBV(OrthoPlayXObj::m_setPostroll)
+XOBJSTUBV(OrthoPlayXObj::m_getPostroll)
+XOBJSTUBV(OrthoPlayXObj::m_setFieldDominance)
+XOBJSTUBNR(OrthoPlayXObj::m_playCue)
+XOBJSTUBNR(OrthoPlayXObj::m_playSegment)
+XOBJSTUBNR(OrthoPlayXObj::m_recordCue)
+XOBJSTUBNR(OrthoPlayXObj::m_recordSegment)
+XOBJSTUBNR(OrthoPlayXObj::m_recordVideoEnable)
+XOBJSTUBNR(OrthoPlayXObj::m_recordAudioEnable)
+XOBJSTUBV(OrthoPlayXObj::m_assembleRecord)
+XOBJSTUBV(OrthoPlayXObj::m_previewRecord)
+XOBJSTUBNR(OrthoPlayXObj::m_gotoInPoint)
+XOBJSTUBNR(OrthoPlayXObj::m_gotoOutPoint)
+XOBJSTUBNR(OrthoPlayXObj::m_gotoPrerollPoint)
+XOBJSTUBNR(OrthoPlayXObj::m_gotoPostrollPoint)
 
 } // End of namespace Director
diff --git a/engines/director/lingo/xlibs/popupmenuxobj.cpp b/engines/director/lingo/xlibs/popupmenuxobj.cpp
index 0e975281b4b..8585b8f2848 100644
--- a/engines/director/lingo/xlibs/popupmenuxobj.cpp
+++ b/engines/director/lingo/xlibs/popupmenuxobj.cpp
@@ -104,6 +104,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/popupmenuxobj.h"
 
 
@@ -158,63 +159,16 @@ void PopUpMenuXObj::m_new(int nargs) {
 	g_lingo->push(g_lingo->_state->me);
 }
 
-void PopUpMenuXObj::m_appendMenu(int nargs) {
-	g_lingo->printSTUBWithArglist("PopUpMenuXObj::m_appendMenu", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void PopUpMenuXObj::m_disableItem(int nargs) {
-	g_lingo->printSTUBWithArglist("PopUpMenuXObj::m_disableItem", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void PopUpMenuXObj::m_enableItem(int nargs) {
-	g_lingo->printSTUBWithArglist("PopUpMenuXObj::m_enableItem", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void PopUpMenuXObj::m_getItem(int nargs) {
-	g_lingo->printSTUBWithArglist("PopUpMenuXObj::m_getItem", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void PopUpMenuXObj::m_getMenuID(int nargs) {
-	g_lingo->printSTUBWithArglist("PopUpMenuXObj::m_getMenuID", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void PopUpMenuXObj::m_popNum(int nargs) {
-	g_lingo->printSTUBWithArglist("PopUpMenuXObj::m_popNum", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void PopUpMenuXObj::m_popText(int nargs) {
-	g_lingo->printSTUBWithArglist("PopUpMenuXObj::m_popText", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void PopUpMenuXObj::m_setItem(int nargs) {
-	g_lingo->printSTUBWithArglist("PopUpMenuXObj::m_setItem", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void PopUpMenuXObj::m_setItemMark(int nargs) {
-	g_lingo->printSTUBWithArglist("PopUpMenuXObj::m_setItemMark", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void PopUpMenuXObj::m_smart(int nargs) {
-	g_lingo->printSTUBWithArglist("PopUpMenuXObj::m_smart", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void PopUpMenuXObj::m_setItemIcon(int nargs) {
-	g_lingo->printSTUBWithArglist("PopUpMenuXObj::m_setItemIcon", nargs);
-	g_lingo->dropStack(nargs);
-}
+XOBJSTUBNR(PopUpMenuXObj::m_appendMenu)
+XOBJSTUBNR(PopUpMenuXObj::m_disableItem)
+XOBJSTUBNR(PopUpMenuXObj::m_enableItem)
+XOBJSTUB(PopUpMenuXObj::m_getItem, "")
+XOBJSTUB(PopUpMenuXObj::m_getMenuID, 0)
+XOBJSTUB(PopUpMenuXObj::m_popNum, 0)
+XOBJSTUB(PopUpMenuXObj::m_popText, "")
+XOBJSTUBNR(PopUpMenuXObj::m_setItem)
+XOBJSTUBNR(PopUpMenuXObj::m_setItemMark)
+XOBJSTUBNR(PopUpMenuXObj::m_smart)
+XOBJSTUBNR(PopUpMenuXObj::m_setItemIcon)
 
 } // End of namespace Director
diff --git a/engines/director/lingo/xlibs/printomatic.cpp b/engines/director/lingo/xlibs/printomatic.cpp
index 9ab947c8c2b..7060e6d2776 100644
--- a/engines/director/lingo/xlibs/printomatic.cpp
+++ b/engines/director/lingo/xlibs/printomatic.cpp
@@ -106,6 +106,7 @@ IS     mRegister, serialNumber
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/printomatic.h"
 
 
@@ -164,57 +165,21 @@ void PrintOMaticXObj::m_new(int nargs) {
 	g_lingo->push(g_lingo->_state->me);
 }
 
-void PrintOMaticXObj::m_dispose(int nargs) {
-	g_lingo->printSTUBWithArglist("PrintOMaticXObj::m_dispose", nargs);
-	g_lingo->dropStack(nargs);
-}
+XOBJSTUBNR(PrintOMaticXObj::m_dispose)
 
 void PrintOMaticXObj::m_register(int nargs) {
 	Common::String serialNumber = g_lingo->pop().asString();
 	warning("PrintOMaticXObj::m_register: Registered with serial \"%s\"", serialNumber.c_str());
 }
 
-void PrintOMaticXObj::m_reset(int nargs) {
-	g_lingo->printSTUBWithArglist("PrintOMaticXObj::m_reset", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void PrintOMaticXObj::m_newPage(int nargs) {
-	g_lingo->printSTUBWithArglist("PrintOMaticXObj::m_newPage", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void PrintOMaticXObj::m_setPrintableMargins(int nargs) {
-	g_lingo->printSTUBWithArglist("PrintOMaticXObj::m_setPrintableMargins", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void PrintOMaticXObj::m_getPageWidth(int nargs) {
-	g_lingo->printSTUBWithArglist("PrintOMaticXObj::m_getPageWidth", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(-1));
-}
-
-void PrintOMaticXObj::m_getPageHeight(int nargs) {
-	g_lingo->printSTUBWithArglist("PrintOMaticXObj::m_getPageHeight", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(-1));
-}
-
-void PrintOMaticXObj::m_picture(int nargs) {
-	g_lingo->printSTUBWithArglist("PrintOMaticXObj::m_picture", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void PrintOMaticXObj::m_stagePicture(int nargs) {
-	g_lingo->printSTUBWithArglist("PrintOMaticXObj::m_stagePicture", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void PrintOMaticXObj::m_1bitStagePicture(int nargs) {
-	g_lingo->printSTUBWithArglist("PrintOMaticXObj::m_1bitStagePicture", nargs);
-	g_lingo->dropStack(nargs);
-}
+XOBJSTUBNR(PrintOMaticXObj::m_reset)
+XOBJSTUB(PrintOMaticXObj::m_newPage, 0)
+XOBJSTUBNR(PrintOMaticXObj::m_setPrintableMargins)
+XOBJSTUB(PrintOMaticXObj::m_getPageWidth, -1)
+XOBJSTUB(PrintOMaticXObj::m_getPageHeight, -1)
+XOBJSTUBV(PrintOMaticXObj::m_picture)
+XOBJSTUBV(PrintOMaticXObj::m_stagePicture)
+XOBJSTUBV(PrintOMaticXObj::m_1bitStagePicture)
 
 void PrintOMaticXObj::m_setLandscapeMode(int nargs) {
 	// int trueOrFalse = g_lingo->pop.asInt()
@@ -222,36 +187,11 @@ void PrintOMaticXObj::m_setLandscapeMode(int nargs) {
 	g_lingo->dropStack(nargs);
 }
 
-void PrintOMaticXObj::m_doPageSetup(int nargs) {
-	g_lingo->printSTUBWithArglist("PrintOMaticXObj::m_doPageSetup", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(1));
-}
-
-void PrintOMaticXObj::m_doJobSetup(int nargs) {
-	g_lingo->printSTUBWithArglist("PrintOMaticXObj::m_doJobSetup", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(1));
-}
-
-void PrintOMaticXObj::m_setProgressMsg(int nargs) {
-	g_lingo->printSTUBWithArglist("PrintOMaticXObj::m_setProgressMsg", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void PrintOMaticXObj::m_printPreview(int nargs) {
-	g_lingo->printSTUBWithArglist("PrintOMaticXObj::m_printPreview", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void PrintOMaticXObj::m_printPicts(int nargs) {
-	g_lingo->printSTUBWithArglist("PrintOMaticXObj::m_printPicts", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void PrintOMaticXObj::m_print(int nargs) {
-	g_lingo->printSTUBWithArglist("PrintOMaticXObj::m_print", nargs);
-	g_lingo->dropStack(nargs);
-}
+XOBJSTUB(PrintOMaticXObj::m_doPageSetup, 1)
+XOBJSTUB(PrintOMaticXObj::m_doJobSetup, 1)
+XOBJSTUBNR(PrintOMaticXObj::m_setProgressMsg)
+XOBJSTUB(PrintOMaticXObj::m_printPreview, 0)
+XOBJSTUBV(PrintOMaticXObj::m_printPicts)
+XOBJSTUBNR(PrintOMaticXObj::m_print)
 
 } // End of namespace Director
diff --git a/engines/director/lingo/xlibs/qtmovie.cpp b/engines/director/lingo/xlibs/qtmovie.cpp
index 19bf4e9e2b8..da0b64c1cc1 100644
--- a/engines/director/lingo/xlibs/qtmovie.cpp
+++ b/engines/director/lingo/xlibs/qtmovie.cpp
@@ -33,6 +33,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/qtmovie.h"
 
 
@@ -57,9 +58,6 @@ void QTMovie::close(int type) {
 	g_lingo->cleanupBuiltIns(builtins);
 }
 
-void QTMovie::m_qtmovie(int nargs) {
-	g_lingo->printSTUBWithArglist("QTMovie::m_qtmovie", nargs);
-	g_lingo->dropStack(nargs);
-}
+XOBJSTUBNR(QTMovie::m_qtmovie)
 
 } // End of namespace Director
diff --git a/engines/director/lingo/xlibs/serialportxobj.cpp b/engines/director/lingo/xlibs/serialportxobj.cpp
index e7ad7d2c542..7e75f2ae0c3 100644
--- a/engines/director/lingo/xlibs/serialportxobj.cpp
+++ b/engines/director/lingo/xlibs/serialportxobj.cpp
@@ -38,6 +38,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/serialportxobj.h"
 
 namespace Director {
@@ -89,66 +90,15 @@ void SerialPortXObj::m_new(int nargs) {
 	g_lingo->push(g_lingo->_state->me);
 }
 
-void SerialPortXObj::m_getPortNum(int nargs) {
-	g_lingo->printSTUBWithArglist("SerialPortXObj::m_getPortNum", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void SerialPortXObj::m_writeString(int nargs) {
-	g_lingo->printSTUBWithArglist("SerialPortXObj::m_writeString", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void SerialPortXObj::m_writeChar(int nargs) {
-	g_lingo->printSTUBWithArglist("SerialPortXObj::m_writeChar", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void SerialPortXObj::m_readString(int nargs) {
-	g_lingo->printSTUBWithArglist("SerialPortXObj::m_readString", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void SerialPortXObj::m_readChar(int nargs) {
-	g_lingo->printSTUBWithArglist("SerialPortXObj::m_readChar", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void SerialPortXObj::m_readCount(int nargs) {
-	g_lingo->printSTUBWithArglist("SerialPortXObj::m_readCount", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void SerialPortXObj::m_readFlush(int nargs) {
-	g_lingo->printSTUBWithArglist("SerialPortXObj::m_readFlush", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void SerialPortXObj::m_configChan(int nargs) {
-	g_lingo->printSTUBWithArglist("SerialPortXObj::m_configChan", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void SerialPortXObj::m_hShakeChan(int nargs) {
-	g_lingo->printSTUBWithArglist("SerialPortXObj::m_hShakeChan", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void SerialPortXObj::m_setUp(int nargs) {
-	g_lingo->printSTUBWithArglist("SerialPortXObj::m_setUp", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-
+XOBJSTUBV(SerialPortXObj::m_getPortNum)
+XOBJSTUBV(SerialPortXObj::m_writeString)
+XOBJSTUBV(SerialPortXObj::m_writeChar)
+XOBJSTUBV(SerialPortXObj::m_readString)
+XOBJSTUBV(SerialPortXObj::m_readChar)
+XOBJSTUBV(SerialPortXObj::m_readCount)
+XOBJSTUBV(SerialPortXObj::m_readFlush)
+XOBJSTUBV(SerialPortXObj::m_configChan)
+XOBJSTUBV(SerialPortXObj::m_hShakeChan)
+XOBJSTUBV(SerialPortXObj::m_setUp)
 
 } // End of namespace Director
diff --git a/engines/director/lingo/xlibs/soundjam.cpp b/engines/director/lingo/xlibs/soundjam.cpp
index 7cf8c1c7508..e7f20996ea5 100644
--- a/engines/director/lingo/xlibs/soundjam.cpp
+++ b/engines/director/lingo/xlibs/soundjam.cpp
@@ -107,11 +107,7 @@ void SoundJam::m_new(int nargs) {
 	g_lingo->push(g_lingo->_state->me);
 }
 
-void SoundJam::m_defineFileSound(int nargs) {
-	g_lingo->printSTUBWithArglist("SoundJam::m_defineFileSound", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
+XOBJSTUB(SoundJam::m_defineFileSound, 0)
 
 void SoundJam::m_defineCastSound(int nargs) {
 	SoundJamObject *me = static_cast<SoundJamObject *>(g_lingo->_state->me.u.obj);
@@ -147,17 +143,8 @@ void SoundJam::m_undefineSound(int nargs) {
 	g_lingo->push(0); // success
 }
 
-void SoundJam::m_readSome(int nargs) {
-	g_lingo->printSTUBWithArglist("SoundJam::m_readSome", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void SoundJam::m_startSound(int nargs) {
-	g_lingo->printSTUBWithArglist("SoundJam::m_startSound", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
+XOBJSTUB(SoundJam::m_readSome, 0)
+XOBJSTUB(SoundJam::m_startSound, 0)
 
 void SoundJam::m_switchNew(int nargs) {
 	SoundJamObject *me = static_cast<SoundJamObject *>(g_lingo->_state->me.u.obj);
@@ -175,22 +162,9 @@ void SoundJam::m_switchNew(int nargs) {
 	g_lingo->push(0); // success
 }
 
-void SoundJam::m_switchParallel(int nargs) {
-	g_lingo->printSTUBWithArglist("SoundJam::m_switchParallel", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void SoundJam::m_hasSwitchHappened(int nargs) {
-	g_lingo->printSTUBWithArglist("SoundJam::m_hasSwitchHappened", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void SoundJam::m_toggleMute(int nargs) {
-	g_lingo->printSTUBWithArglist("SoundJam::m_toggleMute", nargs);
-	g_lingo->dropStack(nargs);
-}
+XOBJSTUB(SoundJam::m_switchParallel, 0)
+XOBJSTUB(SoundJam::m_hasSwitchHappened, 0)
+XOBJSTUBNR(SoundJam::m_toggleMute)
 
 void SoundJam::m_stop(int nargs) {
 	DirectorSound *sound = g_director->getCurrentWindow()->getSoundManager();
diff --git a/engines/director/lingo/xlibs/spacemgr.cpp b/engines/director/lingo/xlibs/spacemgr.cpp
index c149511ee58..2e4782dd982 100644
--- a/engines/director/lingo/xlibs/spacemgr.cpp
+++ b/engines/director/lingo/xlibs/spacemgr.cpp
@@ -76,6 +76,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/spacemgr.h"
 #include "director/util.h"
 
@@ -162,34 +163,11 @@ void SpaceMgr::m_new(int nargs) {
 	g_lingo->push(g_lingo->_state->me);
 }
 
-void SpaceMgr::m_dispose(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_dispose", nargs);
-	g_lingo->dropStack(nargs);
-}
-
-void SpaceMgr::m_lastError(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_lastError", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void SpaceMgr::m_memUsed(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_memUsed", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void SpaceMgr::m_listData(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_listData", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(""));
-}
-
-void SpaceMgr::m_sortAll(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_sortAll", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
+XOBJSTUBNR(SpaceMgr::m_dispose)
+XOBJSTUB(SpaceMgr::m_lastError, 0)
+XOBJSTUB(SpaceMgr::m_memUsed, 0)
+XOBJSTUB(SpaceMgr::m_listData, "")
+XOBJSTUB(SpaceMgr::m_sortAll, 0)
 
 void SpaceMgr::m_checkForDups(int nargs) {
 	if (nargs != 1) {
@@ -354,11 +332,7 @@ void SpaceMgr::m_setCurData(int nargs) {
 	g_lingo->push(Datum(0));
 }
 
-void SpaceMgr::m_addSpaceCollection(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_addSpaceCollection", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
+XOBJSTUB(SpaceMgr::m_addSpaceCollection, 0)
 
 void SpaceMgr::m_removeSpaceCollection(int nargs) {
 	if (nargs != 1) {
@@ -433,17 +407,8 @@ void SpaceMgr::m_getSpaceCollection(int nargs) {
 	g_lingo->push(Datum(result));
 }
 
-void SpaceMgr::m_addSpace(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_addSpace", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void SpaceMgr::m_removeSpace(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_removeSpace", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
+XOBJSTUB(SpaceMgr::m_addSpace, 0)
+XOBJSTUB(SpaceMgr::m_removeSpace, 0)
 
 void SpaceMgr::m_setCurSpace(int nargs) {
 	if (nargs != 1) {
@@ -507,17 +472,8 @@ void SpaceMgr::m_getSpace(int nargs) {
 	g_lingo->push(Datum(result));
 }
 
-void SpaceMgr::m_addNode(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_addNode", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void SpaceMgr::m_removeNode(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_removeNode", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
+XOBJSTUB(SpaceMgr::m_addNode, 0)
+XOBJSTUB(SpaceMgr::m_removeNode, 0)
 
 void SpaceMgr::m_setCurNode(int nargs) {
 	if (nargs != 1) {
@@ -587,17 +543,8 @@ void SpaceMgr::m_getNode(int nargs) {
 	g_lingo->push(Datum(result));
 }
 
-void SpaceMgr::m_addView(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_addView", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void SpaceMgr::m_removeView(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_removeView", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
+XOBJSTUB(SpaceMgr::m_addView, 0)
+XOBJSTUB(SpaceMgr::m_removeView, 0)
 
 void SpaceMgr::m_setCurView(int nargs) {
 	if (nargs != 1) {
@@ -673,23 +620,9 @@ void SpaceMgr::m_getView(int nargs) {
 	g_lingo->push(Datum(result));
 }
 
-void SpaceMgr::m_addLocalLink(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_addLocalLink", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void SpaceMgr::m_removeLocalLink(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_removeLocalLink", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void SpaceMgr::m_removeLocalLinks(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_removeLocalLinks", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
+XOBJSTUB(SpaceMgr::m_addLocalLink, 0)
+XOBJSTUB(SpaceMgr::m_removeLocalLink, 0)
+XOBJSTUB(SpaceMgr::m_removeLocalLinks, 0)
 
 void SpaceMgr::m_getLocalLink(int nargs) {
 	if (nargs != 1) {
@@ -721,34 +654,10 @@ void SpaceMgr::m_getLocalLink(int nargs) {
 	g_lingo->push(Datum(result));
 }
 
-void SpaceMgr::m_getLocalLinks(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_getLocalLinks", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(""));
-}
-
-void SpaceMgr::m_addGlobalLink(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_addGlobalLink", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(""));
-}
-
-void SpaceMgr::m_removeGlobalLink(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_removeGlobalLink", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(0));
-}
-
-void SpaceMgr::m_getGlobalLink(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_getGlobalLink", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(""));
-}
-
-void SpaceMgr::m_getGlobalLinks(int nargs) {
-	g_lingo->printSTUBWithArglist("SpaceMgr::m_getGlobalLinks", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(""));
-}
+XOBJSTUB(SpaceMgr::m_getLocalLinks, "")
+XOBJSTUB(SpaceMgr::m_addGlobalLink, "")
+XOBJSTUB(SpaceMgr::m_removeGlobalLink, 0)
+XOBJSTUB(SpaceMgr::m_getGlobalLink, "")
+XOBJSTUB(SpaceMgr::m_getGlobalLinks, "")
 
 } // End of namespace Director
diff --git a/engines/director/lingo/xlibs/valkyrie.cpp b/engines/director/lingo/xlibs/valkyrie.cpp
index d6bc50b7d5d..9a563eb4acf 100644
--- a/engines/director/lingo/xlibs/valkyrie.cpp
+++ b/engines/director/lingo/xlibs/valkyrie.cpp
@@ -41,6 +41,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/valkyrie.h"
 
 
@@ -88,19 +89,13 @@ void ValkyrieXObj::m_new(int nargs) {
 	g_lingo->push(g_lingo->_state->me);
 }
 
-void ValkyrieXObj::m_dispose(int nargs) {
-	g_lingo->printSTUBWithArglist("ValkyrieXObj::m_dispose", nargs);
-	g_lingo->dropStack(nargs);
-}
+XOBJSTUBNR(ValkyrieXObj::m_dispose)
 
 void ValkyrieXObj::m_name(int nargs) {
 	g_lingo->push(Datum("Valkyrie"));
 }
 
-void ValkyrieXObj::m_status(int nargs) {
-	g_lingo->printSTUBWithArglist("ValkyrieXObj::m_status", nargs);
-	g_lingo->dropStack(nargs);
-}
+XOBJSTUB(ValkyrieXObj::m_status, 0)
 
 void ValkyrieXObj::m_error(int nargs) {
 	// TODO: Save error code for m_lastError?
@@ -108,10 +103,7 @@ void ValkyrieXObj::m_error(int nargs) {
 	warning("ValkyrieXObj::m_error: Got error %d", errorCode);
 }
 
-void ValkyrieXObj::m_lastError(int nargs) {
-	g_lingo->printSTUBWithArglist("ValkyrieXObj::m_lastError", nargs);
-	g_lingo->dropStack(nargs);
-}
+XOBJSTUB(ValkyrieXObj::m_lastError, "")
 
 void ValkyrieXObj::m_save(int nargs) {
 	// should write to namco.ini > Valkyrie > Data
diff --git a/engines/director/lingo/xlibs/videodiscxobj.cpp b/engines/director/lingo/xlibs/videodiscxobj.cpp
index 7b838792e9b..c7ebe9d618d 100644
--- a/engines/director/lingo/xlibs/videodiscxobj.cpp
+++ b/engines/director/lingo/xlibs/videodiscxobj.cpp
@@ -110,6 +110,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/videodiscxobj.h"
 
 namespace Director {
@@ -176,149 +177,29 @@ void VideodiscXObj::m_new(int nargs) {
 	g_lingo->push(g_lingo->_state->me);
 }
 
-void VideodiscXObj::m_name(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_name", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_player(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_player", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_play(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_play", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_playRev(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_playRev", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_fastFwd(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_fastFwd", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_fastRev(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_fastRev", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_slowFwd(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_slowFwd", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_slowRev(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_slowRev", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_stepFwd(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_stepFwd", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_stepRev(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_stepRev", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_playJog(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_playJog", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_playSpeed(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_playSpeed", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_playSegment(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_playSegment", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_pause(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_pause", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_stop(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_stop", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_eject(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_eject", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_stopAtFrame(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_stopAtFrame", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_searchWait(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_searchWait", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_readPos(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_readPos", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_showDisplay(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_showDisplay", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_clear(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_clear", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_videoControl(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_videoControl", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_audioControl(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_audioControl", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
-void VideodiscXObj::m_status(int nargs) {
-	g_lingo->printSTUBWithArglist("VideodiscXObj::m_status", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum());
-}
-
+XOBJSTUBV(VideodiscXObj::m_name)
+XOBJSTUBV(VideodiscXObj::m_player)
+XOBJSTUBV(VideodiscXObj::m_play)
+XOBJSTUBV(VideodiscXObj::m_playRev)
+XOBJSTUBV(VideodiscXObj::m_fastFwd)
+XOBJSTUBV(VideodiscXObj::m_fastRev)
+XOBJSTUBV(VideodiscXObj::m_slowFwd)
+XOBJSTUBV(VideodiscXObj::m_slowRev)
+XOBJSTUBV(VideodiscXObj::m_stepFwd)
+XOBJSTUBV(VideodiscXObj::m_stepRev)
+XOBJSTUBV(VideodiscXObj::m_playJog)
+XOBJSTUBV(VideodiscXObj::m_playSpeed)
+XOBJSTUBV(VideodiscXObj::m_playSegment)
+XOBJSTUBV(VideodiscXObj::m_pause)
+XOBJSTUBV(VideodiscXObj::m_stop)
+XOBJSTUBV(VideodiscXObj::m_eject)
+XOBJSTUBV(VideodiscXObj::m_stopAtFrame)
+XOBJSTUBV(VideodiscXObj::m_searchWait)
+XOBJSTUBV(VideodiscXObj::m_readPos)
+XOBJSTUBV(VideodiscXObj::m_showDisplay)
+XOBJSTUBV(VideodiscXObj::m_clear)
+XOBJSTUBV(VideodiscXObj::m_videoControl)
+XOBJSTUBV(VideodiscXObj::m_audioControl)
+XOBJSTUBV(VideodiscXObj::m_status)
 
 } // End of namespace Director
diff --git a/engines/director/lingo/xlibs/widgetxobj.cpp b/engines/director/lingo/xlibs/widgetxobj.cpp
index 13d5a4c4db0..aa73ac6ee4d 100644
--- a/engines/director/lingo/xlibs/widgetxobj.cpp
+++ b/engines/director/lingo/xlibs/widgetxobj.cpp
@@ -39,6 +39,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/widgetxobj.h"
 
 namespace Director {
@@ -91,9 +92,6 @@ void WidgetXObj::m_getPro(int nargs) {
 	g_lingo->push(Datum("D"));
 }
 
-void WidgetXObj::m_askQuit(int nargs) {
-	g_lingo->printSTUBWithArglist("WidgetXObj::m_askQuit", nargs);
-	g_lingo->dropStack(nargs);
-}
+XOBJSTUB(WidgetXObj::m_askQuit, 0)
 
 } // End of namespace Director
diff --git a/engines/director/lingo/xlibs/xio.cpp b/engines/director/lingo/xlibs/xio.cpp
index bb021310a50..7db98f03dd4 100644
--- a/engines/director/lingo/xlibs/xio.cpp
+++ b/engines/director/lingo/xlibs/xio.cpp
@@ -39,6 +39,7 @@
 #include "director/director.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
 #include "director/lingo/xlibs/xio.h"
 
 
@@ -94,19 +95,7 @@ void XioXObj::m_unlock(int nargs) {
 	g_lingo->push(Datum(1));
 }
 
-void XioXObj::m_deleteFile(int nargs) {
-	// Common::String filename = g_lingo->pop().asString();
-	g_lingo->printSTUBWithArglist("XioXObj::m_deleteFile", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(1));
-}
-
-void XioXObj::m_copyFile(int nargs) {
-	// Common::String source = g_lingo->pop().asString();
-	// Common::String destination = g_lingo->pop().asString();
-	g_lingo->printSTUBWithArglist("XioXObj::m_copyFile", nargs);
-	g_lingo->dropStack(nargs);
-	g_lingo->push(Datum(1));
-}
+XOBJSTUB(XioXObj::m_deleteFile, 1)
+XOBJSTUB(XioXObj::m_copyFile, 1)
 
 } // End of namespace Director


Commit: 2730ccc7edda477afb5ede91a021b98e8f0eb59c
    https://github.com/scummvm/scummvm/commit/2730ccc7edda477afb5ede91a021b98e8f0eb59c
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: LINGO: Make arithmetic issues invoke lingoError()

Changed paths:
    engines/director/lingo/lingo-code.cpp


diff --git a/engines/director/lingo/lingo-code.cpp b/engines/director/lingo/lingo-code.cpp
index 21243a174d9..9009476e3c9 100644
--- a/engines/director/lingo/lingo-code.cpp
+++ b/engines/director/lingo/lingo-code.cpp
@@ -727,7 +727,7 @@ Datum LC::addData(Datum &d1, Datum &d2) {
 	} else if (alignedType == INT) {
 		res = Datum(d1.asInt() + d2.asInt());
 	} else {
-		warning("LC::addData(): not supported between types %s and %s", d1.type2str(), d2.type2str());
+		g_lingo->lingoError("LC::addData(): not supported between types %s and %s", d1.type2str(), d2.type2str());
 	}
 	return res;
 }
@@ -751,7 +751,7 @@ Datum LC::subData(Datum &d1, Datum &d2) {
 	} else if (alignedType == INT) {
 		res = Datum(d1.asInt() - d2.asInt());
 	} else {
-		warning("LC::subData(): not supported between types %s and %s", d1.type2str(), d2.type2str());
+		g_lingo->lingoError("LC::subData(): not supported between types %s and %s", d1.type2str(), d2.type2str());
 	}
 	return res;
 }
@@ -775,7 +775,7 @@ Datum LC::mulData(Datum &d1, Datum &d2) {
 	} else if (alignedType == INT) {
 		res = Datum(d1.asInt() * d2.asInt());
 	} else {
-		warning("LC::mulData(): not supported between types %s and %s", d1.type2str(), d2.type2str());
+		g_lingo->lingoError("LC::mulData(): not supported between types %s and %s", d1.type2str(), d2.type2str());
 	}
 	return res;
 }
@@ -808,7 +808,7 @@ Datum LC::divData(Datum &d1, Datum &d2) {
 	} else if (alignedType == INT) {
 		res = Datum(d1.asInt() / d2.asInt());
 	} else {
-		warning("LC::divData(): not supported between types %s and %s", d1.type2str(), d2.type2str());
+		g_lingo->lingoError("LC::divData(): not supported between types %s and %s", d1.type2str(), d2.type2str());
 	}
 
 	return res;
@@ -828,7 +828,7 @@ Datum LC::modData(Datum &d1, Datum &d2) {
 	int i1 = d1.asInt();
 	int i2 = d2.asInt();
 	if (i2 == 0) {
-		warning("LC::modData(): division by zero");
+		g_lingo->lingoError("LC::modData(): division by zero");
 		i2 = 1;
 	}
 
@@ -860,7 +860,7 @@ Datum LC::negateData(Datum &d) {
 	} else if (d.type == FLOAT) {
 		res = Datum(-d.asFloat());
 	} else {
-		warning("LC::negateData(): not supported for type %s", d.type2str());
+		g_lingo->lingoError("LC::negateData(): not supported for type %s", d.type2str());
 	}
 
 	return res;


Commit: 8cb29bd921b65e4495a07e8c46eff3e616d3fca3
    https://github.com/scummvm/scummvm/commit/8cb29bd921b65e4495a07e8c46eff3e616d3fca3
Author: Scott Percival (code at moral.net.au)
Date: 2023-04-29T14:20:05+02:00

Commit Message:
DIRECTOR: LINGO: Add special override mechanics for List builtins

Fixes the loader in Star Trek TNG Episode Guide

Changed paths:
  A engines/director/lingo/tests/listoverride.lingo
    engines/director/lingo/lingo-builtins.cpp
    engines/director/lingo/lingo-code.cpp
    engines/director/lingo/lingo.h
    engines/director/types.h


diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp
index 66ecea3ecdb..98cb66d656e 100644
--- a/engines/director/lingo/lingo-builtins.cpp
+++ b/engines/director/lingo/lingo-builtins.cpp
@@ -79,31 +79,31 @@ static BuiltinProto builtins[] = {
 	{ "string",			LB::b_string,		1, 1, 200, FBLTIN },	// D2 f
 	{ "value",		 	LB::b_value,		1, 1, 200, FBLTIN },	// D2 f
 	// Lists
-	{ "add",			LB::b_add,			2, 2, 400, HBLTIN },	//			D4 handler
-	{ "addAt",			LB::b_addAt,		3, 3, 400, HBLTIN },	//			D4 h
-	{ "addProp",		LB::b_addProp,		3, 3, 400, HBLTIN },	//			D4 h
-	{ "append",			LB::b_append,		2, 2, 400, HBLTIN },	//			D4 h
-	{ "count",			LB::b_count,		1, 1, 400, FBLTIN },	//			D4 f
-	{ "deleteAt",		LB::b_deleteAt,		2, 2, 400, HBLTIN },	//			D4 h
-	{ "deleteOne",		LB::b_deleteOne,	2, 2, 400, HBLTIN },	//			D4 h, undocumented?
-	{ "deleteProp",		LB::b_deleteProp,	2, 2, 400, HBLTIN },	//			D4 h
-	{ "findPos",		LB::b_findPos,		2, 2, 400, FBLTIN },	//			D4 f
-	{ "findPosNear",	LB::b_findPosNear,	2, 2, 400, FBLTIN },	//			D4 f
-	{ "getaProp",		LB::b_getaProp,		2, 2, 400, FBLTIN },	//			D4 f
-	{ "getAt",			LB::b_getAt,		2, 2, 400, FBLTIN },	//			D4 f
-	{ "getLast",		LB::b_getLast,		1, 1, 400, FBLTIN },	//			D4 f
-	{ "getOne",			LB::b_getOne,		2, 2, 400, FBLTIN },	//			D4 f
-	{ "getPos",			LB::b_getPos,		2, 2, 400, FBLTIN },	//			D4 f
-	{ "getProp",		LB::b_getProp,		2, 2, 400, FBLTIN },	//			D4 f
-	{ "getPropAt",		LB::b_getPropAt,	2, 2, 400, FBLTIN },	//			D4 f
-	{ "list",			LB::b_list,			-1, 0, 400, FBLTIN },	//			D4 f
-	{ "listP",			LB::b_listP,		1, 1, 400, FBLTIN },	//			D4 f
-	{ "max",			LB::b_max,			-1,0, 400, FBLTIN },	//			D4 f
-	{ "min",			LB::b_min,			-1,0, 400, FBLTIN },	//			D4 f
-	{ "setaProp",		LB::b_setaProp,		3, 3, 400, HBLTIN },	//			D4 h
-	{ "setAt",			LB::b_setAt,		3, 3, 400, HBLTIN },	//			D4 h
-	{ "setProp",		LB::b_setProp,		3, 3, 400, HBLTIN },	//			D4 h
-	{ "sort",			LB::b_sort,			1, 1, 400, HBLTIN },	//			D4 h
+	{ "add",			LB::b_add,			2, 2, 400, HBLTIN_LIST },	//			D4 handler
+	{ "addAt",			LB::b_addAt,		3, 3, 400, HBLTIN_LIST },	//			D4 h
+	{ "addProp",		LB::b_addProp,		3, 3, 400, HBLTIN_LIST },	//			D4 h
+	{ "append",			LB::b_append,		2, 2, 400, HBLTIN_LIST },	//			D4 h
+	{ "count",			LB::b_count,		1, 1, 400, FBLTIN_LIST },	//			D4 f
+	{ "deleteAt",		LB::b_deleteAt,		2, 2, 400, HBLTIN_LIST },	//			D4 h
+	{ "deleteOne",		LB::b_deleteOne,	2, 2, 400, HBLTIN_LIST },	//			D4 h, undocumented?
+	{ "deleteProp",		LB::b_deleteProp,	2, 2, 400, HBLTIN_LIST },	//			D4 h
+	{ "findPos",		LB::b_findPos,		2, 2, 400, FBLTIN_LIST },	//			D4 f
+	{ "findPosNear",	LB::b_findPosNear,	2, 2, 400, FBLTIN_LIST },	//			D4 f
+	{ "getaProp",		LB::b_getaProp,		2, 2, 400, FBLTIN_LIST },	//			D4 f
+	{ "getAt",			LB::b_getAt,		2, 2, 400, FBLTIN_LIST },	//			D4 f
+	{ "getLast",		LB::b_getLast,		1, 1, 400, FBLTIN_LIST },	//			D4 f
+	{ "getOne",			LB::b_getOne,		2, 2, 400, FBLTIN_LIST },	//			D4 f
+	{ "getPos",			LB::b_getPos,		2, 2, 400, FBLTIN_LIST },	//			D4 f
+	{ "getProp",		LB::b_getProp,		2, 2, 400, FBLTIN_LIST },	//			D4 f
+	{ "getPropAt",		LB::b_getPropAt,	2, 2, 400, FBLTIN_LIST },	//			D4 f
+	{ "list",			LB::b_list,			-1, 0, 400, FBLTIN_LIST },	//			D4 f
+	{ "listP",			LB::b_listP,		1, 1, 400, FBLTIN_LIST },	//			D4 f
+	{ "max",			LB::b_max,			-1,0, 400, FBLTIN_LIST },	//			D4 f
+	{ "min",			LB::b_min,			-1,0, 400, FBLTIN_LIST },	//			D4 f
+	{ "setaProp",		LB::b_setaProp,		3, 3, 400, HBLTIN_LIST },	//			D4 h
+	{ "setAt",			LB::b_setAt,		3, 3, 400, HBLTIN_LIST },	//			D4 h
+	{ "setProp",		LB::b_setProp,		3, 3, 400, HBLTIN_LIST },	//			D4 h
+	{ "sort",			LB::b_sort,			1, 1, 400, HBLTIN_LIST },	//			D4 h
 	// Files
 	{ "closeDA",	 	LB::b_closeDA, 		0, 0, 200, CBLTIN },	// D2 c
 	{ "closeResFile",	LB::b_closeResFile,	0, 1, 200, CBLTIN },	// D2 c
@@ -260,11 +260,15 @@ void Lingo::initBuiltIns(BuiltinProto protos[]) {
 			_builtinCmds[blt->name] = sym;
 			break;
 		case FBLTIN:
+		case FBLTIN_LIST:
 			_builtinFuncs[blt->name] = sym;
+			_builtinListHandlers[blt->name] = sym;
 			break;
 		case HBLTIN:
+		case HBLTIN_LIST:
 			_builtinCmds[blt->name] = sym;
 			_builtinFuncs[blt->name] = sym;
+			_builtinListHandlers[blt->name] = sym;
 			break;
 		case KBLTIN:
 			_builtinConsts[blt->name] = sym;
diff --git a/engines/director/lingo/lingo-code.cpp b/engines/director/lingo/lingo-code.cpp
index 9009476e3c9..5daddac0d89 100644
--- a/engines/director/lingo/lingo-code.cpp
+++ b/engines/director/lingo/lingo-code.cpp
@@ -1533,6 +1533,16 @@ void LC::call(const Common::String &name, int nargs, bool allowRetVal) {
 	// Handler
 	funcSym = g_lingo->getHandler(name);
 
+	if (g_lingo->_builtinListHandlers.contains(name) && nargs >= 1) {
+		// Lingo builtin functions in the "List" category have very strange override mechanics.
+		// If the first argument is an ARRAY or PARRAY, it will use the builtin.
+		// Otherwise, it will fall back to whatever handler is defined globally.
+		Datum firstArg = g_lingo->peek(nargs - 1);
+		if (firstArg.type == ARRAY || firstArg.type == PARRAY) {
+			funcSym = g_lingo->_builtinListHandlers[name];
+		}
+	}
+
 	if (funcSym.type == VOIDSYM) { // The built-ins could be overridden
 		// Builtin
 		if (allowRetVal) {
diff --git a/engines/director/lingo/lingo.h b/engines/director/lingo/lingo.h
index 2edebbc7578..46a198ac349 100644
--- a/engines/director/lingo/lingo.h
+++ b/engines/director/lingo/lingo.h
@@ -482,6 +482,7 @@ public:
 	SymbolHash _builtinCmds;
 	SymbolHash _builtinFuncs;
 	SymbolHash _builtinConsts;
+	SymbolHash _builtinListHandlers;
 	SymbolHash _methods;
 	XLibFuncHash _xlibOpeners;
 	XLibFuncHash _xlibClosers;
diff --git a/engines/director/lingo/tests/listoverride.lingo b/engines/director/lingo/tests/listoverride.lingo
new file mode 100644
index 00000000000..6b3c22aa114
--- /dev/null
+++ b/engines/director/lingo/tests/listoverride.lingo
@@ -0,0 +1,28 @@
+
+on count l
+  return "what is this"
+end
+
+on add l, x
+  return "this is the worst"
+end
+
+set result = count([1, 2, 3])
+scummvmAssertEqual(result, 3)
+
+set result = count([1: 2, 3: 4, 5: 6])
+scummvmAssertEqual(result, 3)
+
+set result = count("not an array")
+scummvmAssertEqual(result, "what is this")
+
+set result = add("still not an array")
+scummvmAssertEqual(result, "this is the worst")
+
+set result = add("even less of an array", 8)
+scummvmAssertEqual(result, "this is the worst")
+
+set target = [1, 2, 3] 
+add(target, 4)
+scummvmAssertEqual(count(target), 4)
+
diff --git a/engines/director/types.h b/engines/director/types.h
index 5d9ddefc5aa..bd86e562695 100644
--- a/engines/director/types.h
+++ b/engines/director/types.h
@@ -317,6 +317,8 @@ enum SymbolType {
 	FBLTIN,	// builtin function
 	HBLTIN,	// builtin handler (can be called as either command or func)
 	KBLTIN,	// builtin constant
+	FBLTIN_LIST, // builtin function w/list override check
+	HBLTIN_LIST, // builtin handler w/list override check
 	HANDLER	// user-defined handler
 };
 




More information about the Scummvm-git-logs mailing list