[Scummvm-git-logs] scummvm master -> cd35134936c0b0cf2846366f4e1e6daf4f2f9183
sev-
noreply at scummvm.org
Sat Jul 25 13:01:44 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:
55b038f069 DIRECTOR: Resolve child resource owners through one helper
71dab2cd7f DIRECTOR: Gate cast member writers on capability
cd35134936 DIRECTOR: Write back Xtra cast member envelopes
Commit: 55b038f069febc92afe81e9738738716a37b5475
https://github.com/scummvm/scummvm/commit/55b038f069febc92afe81e9738738716a37b5475
Author: Gianluca Boiano (morf3089 at gmail.com)
Date: 2026-07-25T15:01:40+02:00
Commit Message:
DIRECTOR: Resolve child resource owners through one helper
BITD, STXT, CLUT and SCVW each resolved their owning cast member
inline and downcast the result unchecked, so a parent of an
unexpected type wrote through the wrong class. They now share
findResourceOwner(), check the type, and otherwise keep the original
bytes.
Fixes SIGSEGV saving palette_d5_win.dir (director-tests/saving)
Changed paths:
engines/director/archive-save.cpp
engines/director/archive.h
diff --git a/engines/director/archive-save.cpp b/engines/director/archive-save.cpp
index d1ce7e9fb82..46fa4a231e5 100644
--- a/engines/director/archive-save.cpp
+++ b/engines/director/archive-save.cpp
@@ -133,54 +133,38 @@ bool RIFXArchive::writeToFile(Common::String filename, Movie *movie) {
}
break;
- case MKTAG('B', 'I', 'T', 'D'):
- {
- uint32 parentIndex = findParentIndex(it->tag, it->index);
- Resource parent = castResMap[parentIndex];
-
- cast = movie->getCastByLibResourceID(parent.libResourceId);
- BitmapCastMember *target = (BitmapCastMember *)cast->getCastMember(parent.castId + cast->_castArrayStart);
- target->writeBITDResource(saveFile, it->offset);
- }
+ case MKTAG('V', 'W', 'S', 'C'):
+ movie->getScore()->writeVWSCResource(saveFile, it->offset);
break;
+ case MKTAG('B', 'I', 'T', 'D'):
case MKTAG('S', 'T', 'X', 'T'):
- {
- uint32 parentIndex = findParentIndex(it->tag, it->index);
- Resource parent = castResMap[parentIndex];
-
- cast = movie->getCastByLibResourceID(parent.libResourceId);
- TextCastMember *target = (TextCastMember *)cast->getCastMember(parent.castId + cast->_castArrayStart);
- target->writeSTXTResource(saveFile, it->offset);
- }
- break;
-
case MKTAG('C', 'L', 'U', 'T'):
- {
- uint32 parentIndex = findParentIndex(it->tag, it->index);
- Resource parent = castResMap[parentIndex];
-
- cast = movie->getCastByLibResourceID(parent.libResourceId);
- PaletteCastMember *target = (PaletteCastMember *)cast->getCastMember(parent.castId + cast->_castArrayStart);
- target->writePaletteData(saveFile, it->offset);
- }
- break;
-
case MKTAG('S', 'C', 'V', 'W'):
-
{
- uint32 parentIndex = findParentIndex(it->tag, it->index);
- Resource parent = castResMap[parentIndex];
-
- cast = movie->getCastByLibResourceID(parent.libResourceId);
- FilmLoopCastMember *target = (FilmLoopCastMember *)cast->getCastMember(parent.castId + cast->_castArrayStart);
- target->writeSCVWResource(saveFile, it->offset);
+ CastMember *member = findResourceOwner(movie, it->tag, it->index);
+ // The owner is not always the type the tag implies, e.g. a D4
+ // script cast member owns its source text in an 'STXT'
+ if (member) {
+ if (it->tag == MKTAG('B', 'I', 'T', 'D') && member->_type == kCastBitmap) {
+ ((BitmapCastMember *)member)->writeBITDResource(saveFile, it->offset);
+ continue;
+ }
+ if (it->tag == MKTAG('S', 'T', 'X', 'T') && (member->_type == kCastText || member->_type == kCastButton)) {
+ ((TextCastMember *)member)->writeSTXTResource(saveFile, it->offset);
+ continue;
+ }
+ if (it->tag == MKTAG('C', 'L', 'U', 'T') && member->_type == kCastPalette) {
+ ((PaletteCastMember *)member)->writePaletteData(saveFile, it->offset);
+ continue;
+ }
+ if (it->tag == MKTAG('S', 'C', 'V', 'W') && member->_type == kCastFilmLoop) {
+ ((FilmLoopCastMember *)member)->writeSCVWResource(saveFile, it->offset);
+ continue;
+ }
+ }
}
- break;
-
- case MKTAG('V', 'W', 'S', 'C'):
- movie->getScore()->writeVWSCResource(saveFile, it->offset);
- break;
+ // fall through
default:
debugC(7, kDebugSaving, "Saving resource %s as it is, without modification", tag2str(it->tag));
@@ -548,64 +532,61 @@ Common::Array<Resource *> RIFXArchive::rebuildResources(Movie *movie) {
case MKTAG('S', 'T', 'X', 'T'):
{
- uint32 parentIndex = findParentIndex(it->tag, it->index);
- Resource parent = castResMap[parentIndex];
-
- TextCastMember *target = (TextCastMember *)cast->getCastMember(parent.castId + cast->_castArrayStart);
- resSize = target->getSTXTResourceSize();
+ CastMember *member = findResourceOwner(movie, it->tag, it->index);
+ if (member && (member->_type == kCastText || member->_type == kCastButton)) {
+ resSize = ((TextCastMember *)member)->getSTXTResourceSize();
+ it->size = resSize;
+ } else {
+ // Kept verbatim; see the matching case in writeToFile()
+ resSize = it->size;
+ }
it->offset = currentSize;
- it->size = resSize;
-
currentSize += resSize + 8;
}
break;
case MKTAG('C', 'L', 'U', 'T'):
{
- uint32 parentIndex = findParentIndex(it->tag, it->index);
- Resource parent = castResMap[parentIndex];
-
- // Get the appropriate cast in case of multiple casts
- cast = movie->getCastByLibResourceID(parent.libResourceId);
- PaletteCastMember *target = (PaletteCastMember *)cast->getCastMember(parent.castId + cast->_castArrayStart);
- resSize = target->getPaletteDataSize();
+ CastMember *member = findResourceOwner(movie, it->tag, it->index);
+ if (member && member->_type == kCastPalette) {
+ resSize = ((PaletteCastMember *)member)->getPaletteDataSize();
+ it->size = resSize;
+ } else {
+ resSize = it->size;
+ }
it->offset = currentSize;
- it->size = resSize;
-
currentSize += resSize + 8;
}
break;
case MKTAG('B', 'I', 'T', 'D'):
{
- uint32 parentIndex = findParentIndex(it->tag, it->index);
- Resource parent = castResMap[parentIndex];
-
- // Get the appropriate cast in case of multiple casts
- cast = movie->getCastByLibResourceID(parent.libResourceId);
- BitmapCastMember *target = (BitmapCastMember *)cast->getCastMember(parent.castId + cast->_castArrayStart);
- resSize = target->getBITDResourceSize();
+ CastMember *member = findResourceOwner(movie, it->tag, it->index);
+ if (member && member->_type == kCastBitmap) {
+ resSize = ((BitmapCastMember *)member)->getBITDResourceSize();
+ it->size = resSize;
+ } else {
+ resSize = it->size;
+ }
it->offset = currentSize;
- it->size = resSize;
-
currentSize += resSize + 8;
}
break;
case MKTAG('S', 'C', 'V', 'W'):
{
- uint32 parentIndex = findParentIndex(it->tag, it->index);
- Resource parent = castResMap[parentIndex];
-
- FilmLoopCastMember *target = (FilmLoopCastMember *)cast->getCastMember(parent.castId + cast->_castArrayStart);
- resSize = target->getSCVWResourceSize();
+ CastMember *member = findResourceOwner(movie, it->tag, it->index);
+ if (member && member->_type == kCastFilmLoop) {
+ resSize = ((FilmLoopCastMember *)member)->getSCVWResourceSize();
+ it->size = resSize;
+ } else {
+ resSize = it->size;
+ }
it->offset = currentSize;
- it->size = resSize;
-
currentSize += resSize + 8;
}
break;
@@ -728,7 +709,23 @@ uint32 RIFXArchive::findParentIndex(uint32 tag, uint16 index) {
}
warning("RIFXArchive::findParentIndex: The parent for resource: %s, index: %d, was not found", tag2str(tag), index);
- return 0;
+ return kNoParent;
+}
+
+// Resolves the cast member owning a child resource (BITD, STXT, CLUT,
+// SCVW); nullptr when the parent or member is missing
+CastMember *RIFXArchive::findResourceOwner(Movie *movie, uint32 tag, uint16 index) {
+ uint32 parentIndex = findParentIndex(tag, index);
+ if (parentIndex == kNoParent)
+ return nullptr;
+
+ ResourceMap &castResMap = _types[MKTAG('C', 'A', 'S', 't')];
+ if (!castResMap.contains(parentIndex))
+ return nullptr;
+
+ const Resource &parent = castResMap[parentIndex];
+ Cast *cast = movie->getCastByLibResourceID(parent.libResourceId);
+ return cast ? cast->getCastMember(parent.castId + cast->_castArrayStart) : nullptr;
}
SavedArchive::SavedArchive(const Common::String &target) {
diff --git a/engines/director/archive.h b/engines/director/archive.h
index a75816bb9a2..996eda9a548 100644
--- a/engines/director/archive.h
+++ b/engines/director/archive.h
@@ -35,6 +35,9 @@ class Path;
namespace Director {
+class CastMember;
+class Movie;
+
// Completely ripped off of Mohawk's Archive code
struct Resource {
@@ -196,7 +199,9 @@ private:
void readCast(Common::SeekableReadStreamEndian &casStream, uint32 libResourceId);
void readKeyTable(Common::SeekableReadStreamEndian &keyStream);
- uint32 findParentIndex(uint32 tag, uint16 index);
+ static const uint32 kNoParent = 0xFFFFFFFF;
+ uint32 findParentIndex(uint32 tag, uint16 index); // kNoParent when absent
+ CastMember *findResourceOwner(Movie *movie, uint32 tag, uint16 index); // nullptr when unresolvable
/* Memory Map data to save the file */
uint32 _metaTag;
Commit: 71dab2cd7fe4dde01c4923221ca5e8d8e87c8fdd
https://github.com/scummvm/scummvm/commit/71dab2cd7fe4dde01c4923221ca5e8d8e87c8fdd
Author: Gianluca Boiano (morf3089 at gmail.com)
Date: 2026-07-25T15:01:40+02:00
Commit Message:
DIRECTOR: Gate cast member writers on capability
The default CastMember::writeCastData() wrote _castDataSize bytes from
a null pointer, so any member whose writer does not cover the movie's
version corrupted the file. Each type now declares the range its
writer implements through canWriteCastData(); outside it the original
'CASt' bytes are kept, and writeToFile() refuses the save when a
member changed at runtime, or created by Lingo, would lose those
changes. Reporting that needs setModified(), so the remaining direct
_modified assignments go through it.
Fixes null-pointer write on saveMovie in Physicus, whose text Xtra
members have no writer (D7)
Changed paths:
engines/director/archive-save.cpp
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/digitalvideo.cpp
engines/director/castmember/digitalvideo.h
engines/director/castmember/filmloop.cpp
engines/director/castmember/filmloop.h
engines/director/castmember/palette.cpp
engines/director/castmember/palette.h
engines/director/castmember/richtext.cpp
engines/director/castmember/richtext.h
engines/director/castmember/script.cpp
engines/director/castmember/script.h
engines/director/castmember/shape.cpp
engines/director/castmember/shape.h
engines/director/castmember/sound.cpp
engines/director/castmember/sound.h
engines/director/castmember/text.cpp
engines/director/castmember/text.h
engines/director/castmember/transition.cpp
engines/director/castmember/transition.h
engines/director/lingo/xtras-cast/textxtra.cpp
diff --git a/engines/director/archive-save.cpp b/engines/director/archive-save.cpp
index 46fa4a231e5..991d8381d90 100644
--- a/engines/director/archive-save.cpp
+++ b/engines/director/archive-save.cpp
@@ -46,6 +46,15 @@ bool RIFXArchive::writeToFile(Common::String filename, Movie *movie) {
return false;
}
+ // Refuse rather than silently lose runtime changes to members whose
+ // writer can't re-serialize them for this version
+ for (auto &it : *movie->getCasts()) {
+ if (it._value->getArchive().get() == this && it._value->hasUnsavableChanges()) {
+ warning("RIFXArchive::writeToFile(): not saving '%s': modified cast members would lose their changes", movie->getMacName().c_str());
+ return false;
+ }
+ }
+
// If the filename is empty, we save the movie with the name of the current movie
if (filename.empty()) {
filename = movie->getMacName();
@@ -483,10 +492,11 @@ Common::Array<Resource *> RIFXArchive::rebuildResources(Movie *movie) {
// The castIds of cast members start from _castArrayStart
CastMember *target = cast->getCastMember(it->castId + cast->_castArrayStart);
- if (target) {
+ if (target && !cast->keepOriginalCastBytes(target)) {
resSize = target->getCastResourceSize();
it->size = resSize; // getCastResourceSize returns size without header and size
} else {
+ // Members without a version-capable writer keep the original bytes
resSize = it->size;
}
it->offset = currentSize;
diff --git a/engines/director/cast.cpp b/engines/director/cast.cpp
index b1cabd72bda..9b72fcd2330 100644
--- a/engines/director/cast.cpp
+++ b/engines/director/cast.cpp
@@ -274,6 +274,13 @@ bool Cast::duplicateCastMember(CastMember *source, CastMemberInfo *info, int tar
if (!source)
return true;
CastMember *target = source->duplicate(this, targetId);
+ if (!target) {
+ warning("Cast::duplicateCastMember(): could not duplicate %s cast member %d",
+ castType2str(source->_type), source->getID());
+ return false;
+ }
+ // The duplicate reads from the same on-disk resource as the source
+ target->_sourceType = source->_sourceType;
// Some duplicate() implementations don't carry the child resource
// references; they only make sense within the same archive
if (target->_children.empty() && source->getCast() == this)
@@ -952,6 +959,36 @@ void Cast::loadCast() {
}
}
+// Members without a version-capable writer, and members whose in-memory
+// type differs from the type stored on disk (e.g. promoted Xtras), keep
+// their original 'CASt' bytes when saving
+bool Cast::keepOriginalCastBytes(CastMember *target) {
+ if (!target || !target->canWriteCastData())
+ return true;
+ return target->_sourceType != kCastTypeNull && target->_sourceType != target->_type;
+}
+
+// True when a member was changed at runtime (Lingo's `the modified of
+// member`) but has no writer for this version: saving would silently
+// lose the change
+bool Cast::hasUnsavableChanges() {
+ if (!_loadedCast)
+ return false;
+ for (auto &it : *_loadedCast) {
+ CastMember *member = it._value;
+ if (!member || !keepOriginalCastBytes(member))
+ continue;
+ // New (e.g. duplicated) members have no original bytes to copy
+ if (member->isChanged() || member->_index == -1) {
+ warning("Cast::hasUnsavableChanges(): %s cast member %d was %s but has no writer for version v%d",
+ castType2str(member->_type), it._key,
+ member->_index == -1 ? "created at runtime" : "modified", humanVersion(_version));
+ return true;
+ }
+ }
+ return false;
+}
+
void Cast::saveCastData(Common::SeekableWriteStream *writeStream, Resource *res) {
// This offset is at which we will start writing our 'CASt' resources
// In the original file, all the 'CASt' resources don't necessarily appear side by side
@@ -969,8 +1006,17 @@ void Cast::saveCastData(Common::SeekableWriteStream *writeStream, Resource *res)
CastType type = kCastTypeAny;
- if (_loadedCast->contains(id)) {
- CastMember *target = _loadedCast->getVal(id);
+ CastMember *target = _loadedCast->contains(id) ? _loadedCast->getVal(id) : nullptr;
+
+ // Members whose writer doesn't support this version keep their original
+ // 'CASt' bytes; the preflight in writeToFile() already refused the save
+ // if any of them was modified
+ bool keepOriginal = keepOriginalCastBytes(target);
+ if (target && keepOriginal)
+ debugC(5, kDebugSaving, "Cast::saveCastData(): keeping original bytes for %s cast member %d",
+ castType2str(target->_type), id);
+
+ if (target && !keepOriginal) {
// To make it consistent with how the data is stored originally, getResourceSize returns
// the size excluding 'CASt' header and the entry for size itself. Adding 8 to compensate for that
castSize = target->getCastResourceSize();
@@ -1682,6 +1728,7 @@ void Cast::loadCastData(Common::SeekableReadStreamEndian &stream, uint16 id, Res
target->_castDataSize = castDataSize;
target->_flags1 = flags1;
target->_index = res->index;
+ target->_sourceType = (CastType)castType;
setCastMember(id, target);
}
if (castStream.eos()) {
diff --git a/engines/director/cast.h b/engines/director/cast.h
index 7325732d977..61df2732d44 100644
--- a/engines/director/cast.h
+++ b/engines/director/cast.h
@@ -111,6 +111,8 @@ public:
bool importFileInto(int castId, const Common::Path &path);
void saveConfig(Common::SeekableWriteStream *writeStream, uint32 offset, uint32 tag);
+ bool keepOriginalCastBytes(CastMember *target);
+ bool hasUnsavableChanges();
void saveCastData(Common::SeekableWriteStream *writeStream, Resource *res);
void saveCastData();
void writeCastInfo(Common::SeekableWriteStream *writeStream, uint32 castId);
diff --git a/engines/director/castmember/bitmap.cpp b/engines/director/castmember/bitmap.cpp
index 7223a6891c1..43fda27397a 100644
--- a/engines/director/castmember/bitmap.cpp
+++ b/engines/director/castmember/bitmap.cpp
@@ -1139,6 +1139,11 @@ uint32 BitmapCastMember::getCastDataSize() {
return dataSize;
}
+bool BitmapCastMember::canWriteCastData() {
+ // writeCastData() only knows the D4/D5 layout
+ return _cast->_version >= kFileVer400 && _cast->_version < kFileVer600;
+}
+
void BitmapCastMember::writeCastData(Common::SeekableWriteStream *writeStream) {
writeStream->writeUint16BE(_pitch);
@@ -1148,8 +1153,6 @@ void BitmapCastMember::writeCastData(Common::SeekableWriteStream *writeStream) {
writeStream->writeUint16BE(_regY);
writeStream->writeUint16BE(_regX);
- warning("BitmapCastMember::writeCastData(): TODO process D6+");
-
if (_bitsPerPixel != 0) {
writeStream->writeByte(0); // Skip one byte (not stored)
writeStream->writeByte(_bitsPerPixel);
diff --git a/engines/director/castmember/bitmap.h b/engines/director/castmember/bitmap.h
index e346e427ab7..114edec96ec 100644
--- a/engines/director/castmember/bitmap.h
+++ b/engines/director/castmember/bitmap.h
@@ -69,6 +69,7 @@ public:
uint32 writeBITDResource(Common::SeekableWriteStream *writeStream, uint32 offset);
uint32 getCastDataSize() override; // This is the size of the data in the 'CASt' resource
+ bool canWriteCastData() override;
uint32 getBITDResourceSize();
Picture *_picture = nullptr;
diff --git a/engines/director/castmember/castmember.cpp b/engines/director/castmember/castmember.cpp
index abfd8e59f17..7f79c84e8c7 100644
--- a/engines/director/castmember/castmember.cpp
+++ b/engines/director/castmember/castmember.cpp
@@ -285,7 +285,7 @@ void CastMember::setField(int field, const Datum &d) {
castInfo->fileName = filename;
castInfo->directory = rawPath.substr(0, MAX((uint)0, rawPath.size() - filename.size() - 1));
_needsReload = true;
- _modified = true;
+ setModified(true);
}
return;
case kTheForeColor:
@@ -296,7 +296,7 @@ void CastMember::setField(int field, const Datum &d) {
return;
case kTheHilite:
_hilite = (bool)d.asInt();
- _modified = true;
+ setModified(true);
return;
case kTheName:
if (!castInfo) {
@@ -304,6 +304,7 @@ void CastMember::setField(int field, const Datum &d) {
return;
}
castInfo->name = d.asString();
+ setModified(true);
_cast->rebuildCastNameCache();
return;
case kTheRect:
@@ -329,6 +330,7 @@ void CastMember::setField(int field, const Datum &d) {
_cast->_lingoArchive->replaceCode(*d.u.s, scriptType, _castId);
}
castInfo->script = d.asString();
+ setModified(true);
return;
case kTheWidth:
warning("BUILDBOT: CastMember::setField(): Attempt to set read-only field \"%s\" of cast %d", g_lingo->field2str(field), _castId);
@@ -406,28 +408,12 @@ uint32 CastMember::writeCAStResource(Common::SeekableWriteStream *writeStream) {
return 0;
}
-// This is the data that is inside the 'CASt' resource
-// These functions (getCastDataSize() and writeCastData() default implementations, are not supposed to be called
-// If the data is modified in the cast member, we implement a custom getCastDataSize() and writeCastData() for that member
-// If it is not modified, then we write it as it is from the original source in the overridden
-// writeCAStResource(Common::MemoryWriteStream, uint32, uint32) function which doesn't call these default functions
uint32 CastMember::getCastDataSize() {
- warning("CastMember::getDataSize(): Defualt implementation of 'CASt' resource data size");
- return _castDataSize;
+ return 0;
}
void CastMember::writeCastData(Common::SeekableWriteStream *writeStream) {
- warning("CastMember::getDataSize(): Defualt implementation of 'CASt' resource data");
-
- if (_cast->_version >= kFileVer400 && _cast->_version < kFileVer500) {
- if (_flags1 != 0xFF) {
- writeStream->write(0, _castDataSize - 2);
- } else {
- writeStream->write(0, _castDataSize - 1);
- }
- } else {
- writeStream->write(0, _castDataSize);
- }
+ warning("CastMember::writeCastData(): no writer for %s cast member %d", castType2str(_type), _castId);
}
// This is the info that is inside the 'CASt' resource
diff --git a/engines/director/castmember/castmember.h b/engines/director/castmember/castmember.h
index 964e697a944..88afbd2d2a7 100644
--- a/engines/director/castmember/castmember.h
+++ b/engines/director/castmember/castmember.h
@@ -65,6 +65,7 @@ public:
virtual void setEditable(bool editable) {}
virtual bool isModified() { return _modified; }
virtual bool needsReload() { return _needsReload; }
+ bool isChanged() const { return _isChanged; }
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) {}
@@ -111,8 +112,16 @@ public:
uint32 getCastResourceSize();
virtual void writeCastData(Common::SeekableWriteStream *writeStream);
virtual uint32 getCastDataSize();
+ // Whether writeCastData()/getCastDataSize() understand this member for
+ // the movie's version; members without a capable writer keep their
+ // original 'CASt' bytes when saving. New member types must opt in
+ // once their writer is implemented.
+ virtual bool canWriteCastData() { return false; }
CastType _type;
+ // The cast type as stored on disk, which can differ from the in-memory
+ // _type, e.g. when a member was promoted to a more specific class
+ CastType _sourceType = kCastTypeNull;
Common::Rect _initialRect;
Common::Rect _boundingRect;
Common::Array<Resource> _children;
diff --git a/engines/director/castmember/digitalvideo.cpp b/engines/director/castmember/digitalvideo.cpp
index ac63422ed72..523ffae079d 100644
--- a/engines/director/castmember/digitalvideo.cpp
+++ b/engines/director/castmember/digitalvideo.cpp
@@ -900,6 +900,10 @@ void DigitalVideoCastMember::setField(int field, const Datum &d) {
CastMember::setField(field, d);
}
+bool DigitalVideoCastMember::canWriteCastData() {
+ return _cast->_version >= kFileVer400 && _cast->_version < kFileVer1100;
+}
+
uint32 DigitalVideoCastMember::getCastDataSize() {
// We're only reading the _initialRect and _vflags from the Cast Data
// _initialRect : 8 bytes + _vflags : 4 bytes + castType and flags1 (see Cast::loadCastData() for Director 4 only) 2 byte
diff --git a/engines/director/castmember/digitalvideo.h b/engines/director/castmember/digitalvideo.h
index dece8928ce0..bef8577884c 100644
--- a/engines/director/castmember/digitalvideo.h
+++ b/engines/director/castmember/digitalvideo.h
@@ -85,6 +85,7 @@ public:
uint32 getCastDataSize() override;
void writeCastData(Common::SeekableWriteStream *writeStream) override;
+ bool canWriteCastData() override;
Common::String _filename;
diff --git a/engines/director/castmember/filmloop.cpp b/engines/director/castmember/filmloop.cpp
index d6fcc94465c..052952b8a40 100644
--- a/engines/director/castmember/filmloop.cpp
+++ b/engines/director/castmember/filmloop.cpp
@@ -271,6 +271,10 @@ Common::Point FilmLoopCastMember::getRegistrationOffset(int16 currentWidth, int1
return Common::Point(currentWidth / 2, currentHeight / 2);
}
+bool FilmLoopCastMember::canWriteCastData() {
+ return _cast->_version >= kFileVer400 && _cast->_version < kFileVer700;
+}
+
uint32 FilmLoopCastMember::getCastDataSize() {
// We're only reading the _initialRect and _vflags from the Cast Data
// _initialRect : 8 bytes + flags : 4 bytes + 2 bytes unk1 + 2 bytes (castType and _flags1 (see Cast::loadCastData() for Director 4 only)
diff --git a/engines/director/castmember/filmloop.h b/engines/director/castmember/filmloop.h
index 80006e5ef65..1bb2ed2ba70 100644
--- a/engines/director/castmember/filmloop.h
+++ b/engines/director/castmember/filmloop.h
@@ -63,6 +63,7 @@ public:
uint32 getCastDataSize() override;
void writeCastData(Common::SeekableWriteStream *writeStream) override;
+ bool canWriteCastData() override;
void writeSCVWResource(Common::SeekableWriteStream *writeStream, uint32 offset);
uint32 getSCVWResourceSize();
diff --git a/engines/director/castmember/palette.cpp b/engines/director/castmember/palette.cpp
index 60d12236af1..60c61a497c6 100644
--- a/engines/director/castmember/palette.cpp
+++ b/engines/director/castmember/palette.cpp
@@ -138,6 +138,11 @@ void PaletteCastMember::unload() {
}
// PaletteCastMember has no data in the 'CASt' resource or is ignored
+bool PaletteCastMember::canWriteCastData() {
+ // D5-D10 legitimately have no 'CASt' data (it lives in 'CLUT')
+ return _cast->_version >= kFileVer400 && _cast->_version < kFileVer1100;
+}
+
// This is the data in 'CASt' resource
uint32 PaletteCastMember::getCastDataSize() {
if (_cast->_version >= kFileVer500 && _cast->_version < kFileVer1100) {
diff --git a/engines/director/castmember/palette.h b/engines/director/castmember/palette.h
index de5cf1ff98c..ae954862df8 100644
--- a/engines/director/castmember/palette.h
+++ b/engines/director/castmember/palette.h
@@ -45,6 +45,7 @@ public:
uint32 getCastDataSize() override; // This is the size of the data in the 'CASt' resource
void writeCastData(Common::SeekableWriteStream *writeStream) override;
+ bool canWriteCastData() override;
void writePaletteData(Common::SeekableWriteStream *writeStream, uint32 offset);
uint32 getPaletteDataSize();
diff --git a/engines/director/castmember/richtext.cpp b/engines/director/castmember/richtext.cpp
index cbb708fb00f..b29aa879e1e 100644
--- a/engines/director/castmember/richtext.cpp
+++ b/engines/director/castmember/richtext.cpp
@@ -313,4 +313,8 @@ void RichTextCastMember::writeCastData(Common::SeekableWriteStream *writeStream)
}
}
+bool RichTextCastMember::canWriteCastData() {
+ return _cast->_version >= kFileVer500 && _cast->_version < kFileVer1100;
+}
+
} // End of namespace Director
diff --git a/engines/director/castmember/richtext.h b/engines/director/castmember/richtext.h
index e222a25c76f..5c8183d36e9 100644
--- a/engines/director/castmember/richtext.h
+++ b/engines/director/castmember/richtext.h
@@ -48,6 +48,7 @@ public:
Common::String getText() { return Common::String(_plainText); }
uint32 getCastDataSize() override;
void writeCastData(Common::SeekableWriteStream *writeStream) override;
+ bool canWriteCastData() override;
Common::String formatInfo() override;
diff --git a/engines/director/castmember/script.cpp b/engines/director/castmember/script.cpp
index 4d78f052e7e..79859ea3221 100644
--- a/engines/director/castmember/script.cpp
+++ b/engines/director/castmember/script.cpp
@@ -142,6 +142,10 @@ Common::String ScriptCastMember::formatInfo() {
);
}
+bool ScriptCastMember::canWriteCastData() {
+ return _cast->_version >= kFileVer400 && _cast->_version < kFileVer1200;
+}
+
uint32 ScriptCastMember::getCastDataSize() {
if (_cast->_version >= kFileVer400 && _cast->_version < kFileVer500) {
// 2 bytes for type and unk1 + 2 byte for castType and flags ma(see Cast::loadCastData() for Director 4 only
diff --git a/engines/director/castmember/script.h b/engines/director/castmember/script.h
index 26190e2b033..1b00e73005a 100644
--- a/engines/director/castmember/script.h
+++ b/engines/director/castmember/script.h
@@ -41,6 +41,7 @@ public:
uint32 getCastDataSize() override;
void writeCastData(Common::SeekableWriteStream *writeStream) override;
+ bool canWriteCastData() override;
Common::String formatInfo() override;
};
diff --git a/engines/director/castmember/shape.cpp b/engines/director/castmember/shape.cpp
index c2ad578ee43..8a325a5458c 100644
--- a/engines/director/castmember/shape.cpp
+++ b/engines/director/castmember/shape.cpp
@@ -232,6 +232,10 @@ uint32 ShapeCastMember::getCastDataSize() {
}
}
+bool ShapeCastMember::canWriteCastData() {
+ return _cast->_version >= kFileVer400 && _cast->_version < kFileVer1100;
+}
+
void ShapeCastMember::writeCastData(Common::SeekableWriteStream *writeStream) {
writeStream->writeUint16BE((uint16)_shapeType);
diff --git a/engines/director/castmember/shape.h b/engines/director/castmember/shape.h
index bad9b787d81..a6842e5d2d8 100644
--- a/engines/director/castmember/shape.h
+++ b/engines/director/castmember/shape.h
@@ -46,6 +46,7 @@ public:
uint32 getCastDataSize() override;
void writeCastData(Common::SeekableWriteStream *writeStream) override;
+ bool canWriteCastData() override;
ShapeType _shapeType;
uint16 _pattern;
diff --git a/engines/director/castmember/sound.cpp b/engines/director/castmember/sound.cpp
index 0858c9eea47..4c4f6defa6e 100644
--- a/engines/director/castmember/sound.cpp
+++ b/engines/director/castmember/sound.cpp
@@ -300,6 +300,11 @@ void SoundCastMember::setField(int field, const Datum &d) {
CastMember::setField(field, d);
}
+bool SoundCastMember::canWriteCastData() {
+ // D5-D6 legitimately have no 'CASt' data (it lives in 'snd '/'sndH')
+ return _cast->_version >= kFileVer400 && _cast->_version < kFileVer700;
+}
+
// Similar to PaletteCastMember, SoundCastMember has no data in the 'CASt' resource or is ignored
// This is the data in 'CASt' resource
uint32 SoundCastMember::getCastDataSize() {
diff --git a/engines/director/castmember/sound.h b/engines/director/castmember/sound.h
index 1ba605193ec..c23dd6f0ec6 100644
--- a/engines/director/castmember/sound.h
+++ b/engines/director/castmember/sound.h
@@ -47,6 +47,7 @@ public:
uint32 getCastDataSize() override;
void writeCastData(Common::SeekableWriteStream *writeStream) override;
+ bool canWriteCastData() override;
bool _looping;
AudioDecoder *_audio;
diff --git a/engines/director/castmember/text.cpp b/engines/director/castmember/text.cpp
index 8efcab40116..f31db9ceb89 100644
--- a/engines/director/castmember/text.cpp
+++ b/engines/director/castmember/text.cpp
@@ -982,6 +982,11 @@ void TextCastMember::writeCastData(Common::SeekableWriteStream *writeStream) {
}
}
+bool TextCastMember::canWriteCastData() {
+ // writeCastData() is version-agnostic beyond the D4 header difference
+ return _cast->_version >= kFileVer400;
+}
+
uint32 TextCastMember::getCastDataSize() {
// In total 30 bytes for text and 28 for button
uint32 size = (_type == kCastButton) ? 30 : 28;
diff --git a/engines/director/castmember/text.h b/engines/director/castmember/text.h
index dffcb528023..07dfe55e9f5 100644
--- a/engines/director/castmember/text.h
+++ b/engines/director/castmember/text.h
@@ -94,6 +94,7 @@ public:
void writeCastData(Common::SeekableWriteStream *writeStream) override;
uint32 getCastDataSize() override; // This is the size of the data in the 'CASt' resource
+ bool canWriteCastData() override;
uint32 getSTXTResourceSize();
uint32 writeSTXTResource(Common::SeekableWriteStream *writeStream, uint32 offset);
diff --git a/engines/director/castmember/transition.cpp b/engines/director/castmember/transition.cpp
index 078e419584a..19119391270 100644
--- a/engines/director/castmember/transition.cpp
+++ b/engines/director/castmember/transition.cpp
@@ -127,6 +127,10 @@ Common::String TransitionCastMember::formatInfo() {
return Common::String::format("transType: %d, transTime: %d, durationMillis: %d, flags: %d, chunkSize: %d, area: %d", _transType, _transTime, _durationMillis, _flags, _chunkSize, _area);
}
+bool TransitionCastMember::canWriteCastData() {
+ return _cast->_version >= kFileVer500 && _cast->_version < kFileVer1100;
+}
+
uint32 TransitionCastMember::getCastDataSize() {
if (_cast->_version >= kFileVer500 && _cast->_version < kFileVer1100) {
// Ignored 1 byte
diff --git a/engines/director/castmember/transition.h b/engines/director/castmember/transition.h
index e1585c062e0..750820876ce 100644
--- a/engines/director/castmember/transition.h
+++ b/engines/director/castmember/transition.h
@@ -41,6 +41,7 @@ public:
uint32 getCastDataSize() override;
void writeCastData(Common::SeekableWriteStream *writeStream) override;
+ bool canWriteCastData() override;
uint8 _transTime;
TransitionType _transType;
diff --git a/engines/director/lingo/xtras-cast/textxtra.cpp b/engines/director/lingo/xtras-cast/textxtra.cpp
index 840cd89e51c..e511c26862b 100644
--- a/engines/director/lingo/xtras-cast/textxtra.cpp
+++ b/engines/director/lingo/xtras-cast/textxtra.cpp
@@ -213,7 +213,7 @@ void TextXtraCastMember::setField(int field, const Datum &d) {
case kTheText:
_text = Common::U32String(d.asString(), Common::kUtf8);
_loaded = true;
- _modified = true;
+ setModified(true);
return;
default:
break;
Commit: cd35134936c0b0cf2846366f4e1e6daf4f2f9183
https://github.com/scummvm/scummvm/commit/cd35134936c0b0cf2846366f4e1e6daf4f2f9183
Author: Gianluca Boiano (morf3089 at gmail.com)
Date: 2026-07-25T15:01:40+02:00
Commit Message:
DIRECTOR: Write back Xtra cast member envelopes
XtraCastMember only warned instead of writing, so saving dropped the
symbol and payload it had parsed. Both are now written back in the
layout the constructor reads. External members never parse an
envelope, so they keep their original bytes.
Fixes lost 'font' Xtra cast members on saveMovie in Physicus (D7)
Changed paths:
engines/director/castmember/xtra.cpp
engines/director/castmember/xtra.h
diff --git a/engines/director/castmember/xtra.cpp b/engines/director/castmember/xtra.cpp
index a6a41b7f5be..a60dd43d94f 100644
--- a/engines/director/castmember/xtra.cpp
+++ b/engines/director/castmember/xtra.cpp
@@ -145,13 +145,22 @@ Common::String XtraCastMember::formatInfo() {
return Common::String::format("Xtra");
}
+bool XtraCastMember::canWriteCastData() {
+ // External members never parse their envelope (see the constructor)
+ return _cast->_version >= kFileVer500 && !_xtraSymbol.empty();
+}
+
uint32 XtraCastMember::getCastDataSize() {
- warning("XtraCastMember()::getCastDataSize(): CastMember version invalid or not handled");
- return 0;
+ // symbol length + symbol + payload length + payload, as read by the
+ // constructor
+ return 4 + _xtraSymbol.size() + 4 + _xtraData.size();
}
void XtraCastMember::writeCastData(Common::SeekableWriteStream *writeStream) {
- warning("XtraCastMember()::writeCastData(): CastMember version invalid or not handled");
+ writeStream->writeUint32BE(_xtraSymbol.size());
+ writeStream->write(_xtraSymbol.c_str(), _xtraSymbol.size());
+ writeStream->writeUint32BE(_xtraData.size());
+ writeStream->write(_xtraData.data(), _xtraData.size());
}
} // End of namespace Director
diff --git a/engines/director/castmember/xtra.h b/engines/director/castmember/xtra.h
index fea60fe5c14..ec6e8dd8132 100644
--- a/engines/director/castmember/xtra.h
+++ b/engines/director/castmember/xtra.h
@@ -46,6 +46,7 @@ public:
uint32 getCastDataSize() override;
void writeCastData(Common::SeekableWriteStream *writeStream) override;
+ bool canWriteCastData() override;
private:
Common::String _xtraSymbol;
More information about the Scummvm-git-logs
mailing list