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

sev- noreply at scummvm.org
Mon Jul 13 17:06:03 UTC 2026


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

Summary:
7c68cb06b6 DIRECTOR: Parse MCsL through its offset table
8069ae99cb DIRECTOR: Resolve external cast libResourceId from its own archive
fd7429ec0b DIRECTOR: Guard cast lib swaps against partially loaded movies


Commit: 7c68cb06b6ba42da17613c30f2d036222176fbe0
    https://github.com/scummvm/scummvm/commit/7c68cb06b6ba42da17613c30f2d036222176fbe0
Author: Gianluca Boiano (morf3089 at gmail.com)
Date: 2026-07-13T19:05:57+02:00

Commit Message:
DIRECTOR: Parse MCsL through its offset table

MCsL is a list chunk; walking it linearly required guessing
per-version field widths and consulting the game version, which is
not reliably known when the mapping is parsed (Movie::setArchive
runs before the config is loaded, and fallback detection may report
no version at all). Parse it through its offset table instead, like
ProjectorRays' CastListChunk: libResourceId is a 32-bit field in
every version, D5/D6 movies simply store 0 in the high word.
Pre-D7 layout is reference-verified against ProjectorRays, not
against a real D5/D6 sample.

Fixes empty cast (MCsL libResourceId read as 1024 while CASt
resources carry 66560) in bioscopia MrmLod.dir run standalone

Changed paths:
    engines/director/movie.cpp


diff --git a/engines/director/movie.cpp b/engines/director/movie.cpp
index 17473427d3b..779ec606ed7 100644
--- a/engines/director/movie.cpp
+++ b/engines/director/movie.cpp
@@ -156,43 +156,71 @@ void Movie::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();
-	}
+	// MCsL is a "list chunk": header gives per-cast-library field count and
+	// stride; the actual field bytes are found via an offset table located
+	// at dataOffset. libResourceId is 32-bit in all Director versions;
+	// D5/D6 movies simply store 0 in the high word.
+	uint32 dataOffset = stream.readUint32();
+	stream.readUint16(); // unknown
+	uint32 count = stream.readUint16();
+	uint32 itemsPerCast = stream.readUint16();
+
+	stream.seek(dataOffset);
+	uint16 offsetTableLen = stream.readUint16();
+	Common::Array<uint32> offsets(offsetTableLen);
+	for (uint16 i = 0; i < offsetTableLen; i++)
+		offsets[i] = stream.readUint32();
+	uint32 itemsLen = stream.readUint32();
+	uint32 itemsBase = stream.pos();
+
+	auto itemSpan = [&](uint32 idx, uint32 &start, uint32 &end) {
+		start = idx < offsetTableLen ? offsets[idx] : itemsLen;
+		end = (idx + 1) < offsetTableLen ? offsets[idx + 1] : itemsLen;
+		start = MIN<uint32>(start, itemsLen);
+		end = MIN<uint32>(end, itemsLen);
+		if (end < start)
+			end = start;
+	};
+
 	for (uint32 i = 0; i < count; i++) {
-		int nameSize = stream.readByte();
-		Common::String name = stream.readString('\0', nameSize);
-		stream.readByte(); // null
-		int pathSize = stream.readByte();
-		Common::String path = stream.readString('\0', pathSize);
-		stream.readByte(); // null
-		if (pathSize > 1) {
-			// Separator after an external cast path: 1 byte in D7, uint16 in D5/D6.
-			// Not verified for D8+; falls back to the D7 (1-byte) guess with a warning.
-			uint16 ver = g_director->getVersion();
-			if (ver >= 700 && ver < 800) {
-				stream.readByte();
-			} else if (ver >= 800) {
-				warning("STUB: Movie::loadCastLibMapping(): cast-lib path separator width not verified for version v%d", ver);
-				stream.readByte();
-			} else {
-				stream.readUint16();
+		uint32 base = i * itemsPerCast;
+		uint32 start, end;
+
+		Common::String name;
+		itemSpan(base + 1, start, end);
+		if (end - start >= 1) {
+			stream.seek(itemsBase + start);
+			int nameSize = stream.readByte();
+			name = stream.readString('\0', MIN<uint32>(nameSize, end - start - 1));
+		}
+
+		Common::String path;
+		if (itemsPerCast >= 2) {
+			itemSpan(base + 2, start, end);
+			if (end - start >= 1) {
+				stream.seek(itemsBase + start);
+				int pathSize = stream.readByte();
+				path = stream.readString('\0', MIN<uint32>(pathSize, end - start - 1));
 			}
 		}
-		uint16 minMember = stream.readUint16();
-		uint16 maxMember = stream.readUint16();
-		uint32 libResourceId;
-		if (g_director->getVersion() >= 700) {
-			// D7+: single 32-bit resource id; verified against a D7 specimen
-			// (0x00010402) that a 16-bit read would truncate.
-			libResourceId = stream.readUint32();
-		} else {
-			stream.readUint16(); // unknown/reserved; 0 in tested D5/D6 movies
-			libResourceId = stream.readUint16();
+
+		if (itemsPerCast >= 3) {
+			itemSpan(base + 3, start, end);
+			if (end - start >= 2) {
+				stream.seek(itemsBase + start);
+				stream.readUint16(); // preload settings; unused
+			}
+		}
+
+		uint16 minMember = 0, maxMember = 0;
+		uint32 libResourceId = 1024;
+		if (itemsPerCast >= 4) {
+			itemSpan(base + 4, start, end);
+			uint32 span = end - start;
+			stream.seek(itemsBase + start);
+			minMember = span >= 2 ? stream.readUint16() : 0;
+			maxMember = span >= 4 ? stream.readUint16() : 0;
+			libResourceId = span >= 8 ? stream.readUint32() : (span >= 6 ? stream.readUint16() : 1024);
 		}
 		uint16 libId = i + 1;
 		debugC(5, kDebugLoading, "Movie::loadCastLibMapping: name: %s, path: %s, minMember: %d, maxMember: %d, libResourceId: %d, libId: %d", utf8ToPrintable(name).c_str(), utf8ToPrintable(path).c_str(), minMember, maxMember, libResourceId, libId);


Commit: 8069ae99cb1d248307684df16570f42f76379858
    https://github.com/scummvm/scummvm/commit/8069ae99cb1d248307684df16570f42f76379858
Author: Gianluca Boiano (morf3089 at gmail.com)
Date: 2026-07-13T19:05:57+02:00

Commit Message:
DIRECTOR: Resolve external cast libResourceId from its own archive

Cast::loadCast() assumed 1024 for external casts, which only holds
for D5/D6 cast files. D7+ files key their CAS* to ids like
0x00010400, so once the id was no longer truncated to uint16 every
member of an external cast was skipped.

Fixes loading D7+ external cast libraries, e.g. swapping
'the fileName of castLib' to MrmLod.dir in bioscopia

Changed paths:
    engines/director/cast.cpp


diff --git a/engines/director/cast.cpp b/engines/director/cast.cpp
index 31bfcad92aa..b499a056526 100644
--- a/engines/director/cast.cpp
+++ b/engines/director/cast.cpp
@@ -829,9 +829,16 @@ void Cast::loadCast() {
 	if (!_loadedCast)
 		_loadedCast = new Common::HashMap<int, CastMember *>();
 
-	// External casts only have one library ID, so instead
-	// we use the movie's mapping.
-	uint32 libResourceId = _isExternal ? 1024 : _libResourceId;
+	// External casts have a single library. Its resource id lives in the
+	// cast file's own KEY* table (1024 in D5/D6, e.g. 0x00010400 in D7+),
+	// so resolve it from the archive's CAS* resource instead of assuming 1024.
+	uint32 libResourceId = _libResourceId;
+	if (_isExternal) {
+		libResourceId = 1024;
+		Common::Array<uint16> casStar = _castArchive->getResourceIDList(MKTAG('C', 'A', 'S', '*'));
+		if (!casStar.empty())
+			libResourceId = _castArchive->getResourceDetail(MKTAG('C', 'A', 'S', '*'), casStar[0]).libResourceId;
+	}
 
 	if (cast.size() > 0) {
 		debugC(2, kDebugLoading, "****** Loading CASt resources for libId %d (%s), resourceId %d", _castLibID, _castName.c_str(), libResourceId);
@@ -841,8 +848,6 @@ void Cast::loadCast() {
 		for (auto &iterator : cast) {
 			Resource res = _castArchive->getResourceDetail(MKTAG('C', 'A', 'S', 't'), iterator);
 			// Only load cast members which belong to the requested library ID.
-			// External casts only have one library ID, so instead
-			// we use the movie's mapping.
 			if (res.libResourceId != libResourceId) {
 				debugC(5, kDebugLoading, "SKIPPED - CASt: resource %d, castId %d, libResourceId %d", iterator, res.castId, res.libResourceId);
 				continue;


Commit: fd7429ec0b8020e240bbdd192d840c5f044aa79d
    https://github.com/scummvm/scummvm/commit/fd7429ec0b8020e240bbdd192d840c5f044aa79d
Author: Gianluca Boiano (morf3089 at gmail.com)
Date: 2026-07-13T19:05:57+02:00

Commit Message:
DIRECTOR: Guard cast lib swaps against partially loaded movies

Lingo may change the fileName of a castLib before the movie or
score finished loading (e.g. from a LINGO.INI startUp handler).
Guard the archive, current-frame and current-cast dereferences on
that path, and keep Movie::_cast pointing at the replacement when
the default lib is swapped instead of leaving it dangling.

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


diff --git a/engines/director/lingo/lingo-the.cpp b/engines/director/lingo/lingo-the.cpp
index 08c2f195937..eb9bf8e18bd 100644
--- a/engines/director/lingo/lingo-the.cpp
+++ b/engines/director/lingo/lingo-the.cpp
@@ -2234,7 +2234,12 @@ Datum Lingo::getTheCastLib(Datum &id1, int field) {
 
 	switch (field) {
 	case kTheFileName:
-		d = cast->getArchive()->getPathName().toString(g_director->_dirSeparator);
+		// The cast archive may not be attached yet, e.g. when Lingo runs
+		// before the movie finished loading.
+		if (cast->getArchive())
+			d = cast->getArchive()->getPathName().toString(g_director->_dirSeparator);
+		else
+			d = Common::String();
 		break;
 	case kTheName:
 		d = cast->getCastName();
diff --git a/engines/director/movie.cpp b/engines/director/movie.cpp
index 779ec606ed7..6ab5c351c71 100644
--- a/engines/director/movie.cpp
+++ b/engines/director/movie.cpp
@@ -527,7 +527,9 @@ Common::SharedPtr<Archive> Movie::loadExternalCastFrom(Common::Path &filename) {
 bool Movie::loadCastLibFrom(uint16 libId, Common::Path &filename) {
 	if (_casts.contains(libId)) {
 		Cast *cast = _casts[libId];
-		if (cast->getArchive()->getPathName() == filename) {
+		// The cast may not have an archive attached yet, e.g. when Lingo
+		// changes the fileName of a castLib before the movie finished loading.
+		if (cast->getArchive() && cast->getArchive()->getPathName() == filename) {
 			// CastLib is already loaded, change nothing
 			return false;
 		}
@@ -540,11 +542,13 @@ bool Movie::loadCastLibFrom(uint16 libId, Common::Path &filename) {
 
 	uint32 libResourceId = 1024;
 	Common::String name;
+	bool replacingDefault = false;
 	if (_casts.contains(libId)) {
-		Cast *cast = _casts[libId];
-		libResourceId = cast->_libResourceId;
-		name = cast->getCastName();
-		delete cast;
+		Cast *oldCast = _casts[libId];
+		libResourceId = oldCast->_libResourceId;
+		name = oldCast->getCastName();
+		replacingDefault = (oldCast == _cast);
+		delete oldCast;
 		_casts.erase(libId);
 	}
 
@@ -554,6 +558,9 @@ bool Movie::loadCastLibFrom(uint16 libId, Common::Path &filename) {
 	cast->loadCast();
 
 	_casts.setVal(libId, cast);
+	// Keep the default-cast shortcut from dangling when lib 1 is swapped.
+	if (replacingDefault)
+		_cast = cast;
 	_score->refreshPointersForCastLib(libId);
 	return true;
 }
@@ -847,8 +854,11 @@ Common::String InfoEntry::readString(bool pascal) {
 			encodedStr += data[i];
 	}
 
-	// FIXME: Use the case which contains this string, not the main cast.
-	return g_director->getCurrentMovie()->getCast()->decodeString(encodedStr).encode(Common::kUtf8);
+	// FIXME: Use the cast which contains this string, not the main cast.
+	Movie *movie = g_director->getCurrentMovie();
+	if (!movie || !movie->getCast())
+		return encodedStr; // no cast to decode against yet
+	return movie->getCast()->decodeString(encodedStr).encode(Common::kUtf8);
 }
 
 void InfoEntry::writeString(Common::String string, bool pascal) {
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index 14c66573008..39e6864af10 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -1769,6 +1769,11 @@ bool Score::refreshPointersForCastLib(uint16 castLib) {
 		}
 	}
 
+	// The frame may not be loaded yet, e.g. when Lingo swaps a cast lib
+	// before the score started playing.
+	if (!_currentFrame)
+		return hit;
+
 	for (auto &it : _currentFrame->_sprites) {
 		if (it->_castId.castLib == castLib) {
 			it->_cast = nullptr;




More information about the Scummvm-git-logs mailing list