[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