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

sev- noreply at scummvm.org
Thu Apr 30 18:42:55 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:
07bc0bf16b DIRECTOR: XOBJ: Only modify files if data has been written
09b4a2b241 DIRECTOR: Improve accuracy of c_intersects
a97b623ee6 DIRECTOR: Fix handling of frozen play state


Commit: 07bc0bf16b647914370948fd92e39bcedab35688
    https://github.com/scummvm/scummvm/commit/07bc0bf16b647914370948fd92e39bcedab35688
Author: Scott Percival (code at moral.net.au)
Date: 2026-04-30T20:42:48+02:00

Commit Message:
DIRECTOR: XOBJ: Only modify files if data has been written

A big difference FileIO has compared to POSIX file handles is that if
you open a file in write mode, it will only get zeroed/overwritten if
the handle actually writes some data.

Fixes the save game detector in Mission to Planet X, which detects if
MISSION.DAT is on a CD drive by opening a write handle, checking status()
then throwing it away.

Changed paths:
    engines/director/game-quirks.cpp
    engines/director/lingo/xlibs/f/fileio.cpp
    engines/director/lingo/xlibs/f/fileio.h


diff --git a/engines/director/game-quirks.cpp b/engines/director/game-quirks.cpp
index 30d567cbf73..ecd6081eff5 100644
--- a/engines/director/game-quirks.cpp
+++ b/engines/director/game-quirks.cpp
@@ -153,6 +153,7 @@ struct SaveFilePath {
 	const char *path;
 } const saveFilePaths[] = {
 	{ "darkeye", Common::kPlatformWindows, "SAVEDDKY/" },
+	{ "missionplanetx", Common::kPlatformWindows, "" },
 	{"simpsonsstudio", Common::kPlatformWindows, "SIMPSONS/SUPPORT/TOONDATA/"},
 	{"simpsonsstudio", Common::kPlatformMacintosh, "SIMPSONS/SUPPORT/TOONDATA/"},
 	{ nullptr, Common::kPlatformUnknown, nullptr },
diff --git a/engines/director/lingo/xlibs/f/fileio.cpp b/engines/director/lingo/xlibs/f/fileio.cpp
index 28e501dd0a8..71f0645908c 100644
--- a/engines/director/lingo/xlibs/f/fileio.cpp
+++ b/engines/director/lingo/xlibs/f/fileio.cpp
@@ -208,7 +208,6 @@ FileObject::FileObject(ObjectType objType) : Object<FileObject>("FileIO") {
 	_objType = objType;
 	_filename = nullptr;
 	_inStream = nullptr;
-	_outFile = nullptr;
 	_outStream = nullptr;
 	_lastError = kErrorNone;
 }
@@ -217,7 +216,6 @@ FileObject::FileObject(const FileObject &obj) : Object<FileObject>(obj) {
 	_objType = obj.getObjType();
 	_filename = nullptr;
 	_inStream = nullptr;
-	_outFile = nullptr;
 	_outStream = nullptr;
 	_lastError = kErrorNone;
 }
@@ -288,12 +286,8 @@ FileIOError FileObject::open(const Common::String &origpath, const Common::Strin
 		}
 	} else if (option.equalsIgnoreCase("write")) {
 		// OutSaveFile is not seekable so create a separate seekable stream
-		// which will be written to the _outFile upon disposal
-		_outFile = saves->openForSaving(filename, false);
+		// which will be written to the save file upon disposal
 		_outStream = new Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES);
-		if (!_outFile) {
-			return saveFileError();
-		}
 	} else if (option.equalsIgnoreCase("append")) {
 		Common::SeekableReadStream *inFile = saves->openForLoading(filename);
 		if (!inFile) {
@@ -311,10 +305,6 @@ FileIOError FileObject::open(const Common::String &origpath, const Common::Strin
 			b = inFile->readByte();
 		}
 		delete inFile;
-		_outFile = saves->openForSaving(filename, false);
-		if (!_outFile) {
-			return saveFileError();
-		}
 	} else {
 		error("Unsupported FileIO option: '%s'", option.c_str());
 	}
@@ -324,6 +314,21 @@ FileIOError FileObject::open(const Common::String &origpath, const Common::Strin
 }
 
 void FileObject::clear() {
+	if (_outStream) {
+		// When opening a file in write mode with FileIO, any existing data is only destroyed
+		// after the first write. In order to be compatible with the POSIX expectation that
+		// opening a write handle destroys the file, we need to defer the actual save operation
+		// until after we know data has been written.
+		if (_outStream->size()) {
+			Common::SaveFileManager *saves = g_system->getSavefileManager();
+			Common::OutSaveFile *outFile = saves->openForSaving(*_filename, false);
+			outFile->write(_outStream->getData(), _outStream->size());
+			outFile->finalize();
+			delete outFile;
+		}
+		delete _outStream;
+		_outStream = nullptr;
+	}
 	if (_filename) {
 		delete _filename;
 		_filename = nullptr;
@@ -332,14 +337,6 @@ void FileObject::clear() {
 		delete _inStream;
 		_inStream = nullptr;
 	}
-	if (_outFile) {
-		_outFile->write(_outStream->getData(), _outStream->size());
-		_outFile->finalize();
-		delete _outFile;
-		delete _outStream;
-		_outFile = nullptr;
-		_outStream = nullptr;
-	}
 }
 
 void FileObject::dispose() {
diff --git a/engines/director/lingo/xlibs/f/fileio.h b/engines/director/lingo/xlibs/f/fileio.h
index cbfc66d6397..056f530a0c5 100644
--- a/engines/director/lingo/xlibs/f/fileio.h
+++ b/engines/director/lingo/xlibs/f/fileio.h
@@ -55,7 +55,6 @@ class FileObject : public Object<FileObject> {
 public:
 	Common::String *_filename;
 	Common::SeekableReadStream *_inStream;
-	Common::OutSaveFile *_outFile;
 	Common::MemoryWriteStreamDynamic *_outStream;
 	FileIOError _lastError;
 


Commit: 09b4a2b24116ffc701002f3ad773a0bf320d05da
    https://github.com/scummvm/scummvm/commit/09b4a2b24116ffc701002f3ad773a0bf320d05da
Author: Scott Percival (code at moral.net.au)
Date: 2026-04-30T20:42:48+02:00

Commit Message:
DIRECTOR: Improve accuracy of c_intersects

Fixes showing the elevator controls after using the first lift on Planet
X in Mission to Planet X.

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 987abadbc11..ab0ecce0d8a 100644
--- a/engines/director/lingo/lingo-code.cpp
+++ b/engines/director/lingo/lingo-code.cpp
@@ -1006,11 +1006,16 @@ void LC::c_intersects() {
 	// just S1 matte: do a box-on-box intersection
 	// just S2 matte: do a box-on-matte intersection
 	// neither sprite matte: do a box-on-box intersection
+	// If the cast member is not a bitmap, always treat it as a bounding box collision,
+	// even if the ink type is matte and there's visibly no overlap (e.g a kCastShape circle).
+
+	bool s1IsBitmap = sprite1->_sprite->_cast && sprite1->_sprite->_cast->_type == kCastBitmap;
+	bool s2IsBitmap = sprite2->_sprite->_cast && sprite2->_sprite->_cast->_type == kCastBitmap;
 
 	// don't regard quick draw shape as matte type
-	if ((!sprite1->_sprite->isQDShape() && sprite1->_sprite->_ink == kInkTypeMatte) && (!sprite2->_sprite->isQDShape() && sprite2->_sprite->_ink == kInkTypeMatte)) {
+	if ((s1IsBitmap && sprite1->_sprite->_ink == kInkTypeMatte) && (s2IsBitmap && sprite2->_sprite->_ink == kInkTypeMatte)) {
 		g_lingo->push(Datum(sprite2->isMatteIntersect(sprite1)));
-	} else if ((!sprite2->_sprite->isQDShape() && sprite2->_sprite->_ink == kInkTypeMatte)) {
+	} else if ((s2IsBitmap && sprite2->_sprite->_ink == kInkTypeMatte)) {
 		g_lingo->push(Datum(sprite2->isMatteBoxIntersect(sprite1)));
 	} else {
 		g_lingo->push(Datum(sprite2->getBbox().intersects(sprite1->getBbox())));


Commit: a97b623ee6cf582feaf3af0d60dcaa34f321b85a
    https://github.com/scummvm/scummvm/commit/a97b623ee6cf582feaf3af0d60dcaa34f321b85a
Author: Scott Percival (code at moral.net.au)
Date: 2026-04-30T20:42:48+02:00

Commit Message:
DIRECTOR: Fix handling of frozen play state

Due to a change in how _playDone works, the frozen play state was never
getting defrosted. The new approach is to add it to the front of the
regular frozen state list; this way it should execute at the right
time (after the startMovie script but before any frame scripts), and it
will be covered by Window::frozenLingoStateCount().

Fixes old maid game in Gus Goes to the Kooky Carnival.
Confirmed to work with room switching in Cyber Grannies and
switching back from The Smasher in Dr. Health'n'stein's Body Fun.

Changed paths:
    engines/director/lingo/lingo-code.cpp
    engines/director/lingo/lingo.cpp
    engines/director/lingo/lingo.h
    engines/director/score.cpp
    engines/director/score.h
    engines/director/window.cpp
    engines/director/window.h


diff --git a/engines/director/lingo/lingo-code.cpp b/engines/director/lingo/lingo-code.cpp
index ab0ecce0d8a..955a1f3226b 100644
--- a/engines/director/lingo/lingo-code.cpp
+++ b/engines/director/lingo/lingo-code.cpp
@@ -381,6 +381,12 @@ void Lingo::freezePlayState() {
 	switchStateFromWindow();
 }
 
+void Lingo::requeuePlayState() {
+	Window *window = _vm->getCurrentWindow();
+	window->requeueLingoPlayState();
+}
+
+
 void LC::c_constpush() {
 	Common::String name(g_lingo->readString());
 
diff --git a/engines/director/lingo/lingo.cpp b/engines/director/lingo/lingo.cpp
index 146be8eeb44..e185a8f48e0 100644
--- a/engines/director/lingo/lingo.cpp
+++ b/engines/director/lingo/lingo.cpp
@@ -724,6 +724,7 @@ bool Lingo::execute(int targetFrame) {
 		}
 		if (_playDone) {
 			_playDone = false;
+			requeuePlayState();
 		}
 	}
 	_abort = false;
diff --git a/engines/director/lingo/lingo.h b/engines/director/lingo/lingo.h
index 93d8bbe7dd7..53efcf298a1 100644
--- a/engines/director/lingo/lingo.h
+++ b/engines/director/lingo/lingo.h
@@ -420,6 +420,7 @@ public:
 	void switchStateFromWindow();
 	void freezeState();
 	void freezePlayState();
+	void requeuePlayState();
 	void pushContext(const Symbol funcSym, bool allowRetVal, Datum defaultRetVal, int paramCount, int nargs);
 	void popContext(bool aborting = false);
 	void cleanLocalVars();
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index c23975d7126..ffbcb37803e 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -138,38 +138,10 @@ bool Score::processImmediateFrameScript(Common::String s, int id) {
 	return false;
 }
 
-bool Score::processFrozenPlayScript() {
-	// Unfreeze the play script if the special flag is set
-	if (g_lingo->_playDone) {
-		g_lingo->_playDone = false;
-		if (_window->thawLingoPlayState()) {
-			Symbol currentScript;
-			LingoState *state = _window->getLingoState();
-			if (state && !state->callstack.empty())
-				currentScript = state->callstack.front()->sp;
-			g_lingo->switchStateFromWindow();
-			bool completed = g_lingo->execute();
-			if (!completed) {
-				debugC(3, kDebugLingoExec, "Score::processFrozenPlayScript(): State froze again mid-thaw, interrupting");
-				return false;
-			} else if (currentScript == g_lingo->_currentInputEvent) {
-				// script that just completed was the current input event, clear the flag
-				debugC(3, kDebugEvents, "Score::processFrozenPlayScript(): Input event completed");
-				g_lingo->_currentInputEvent = Symbol();
-			}
-		}
-	}
-	return true;
-}
-
-
 bool Score::processFrozenScripts(bool recursion, int count) {
 	if (!_haveInteractivity)
 		return true;
 
-	if (!processFrozenPlayScript())
-		return false;
-
 	// Unfreeze any in-progress scripts and attempt to run them
 	// to completion.
 	bool limit = count != 0;
@@ -485,6 +457,7 @@ void Score::updateCurrentFrame() {
 
 	if (nextFrameNumberToLoad >= getFramesNum()) {
 		Window *window = _vm->getCurrentWindow();
+		// reached the end of the movie
 		if (!window->_movieStack.empty()) {
 			MovieReference ref = window->_movieStack.back();
 			window->_movieStack.pop_back();
@@ -494,6 +467,9 @@ void Score::updateCurrentFrame() {
 				window->_nextMovie.frameI = ref.frameI;
 				return;
 			}
+			if (window->getLingoPlayState()) {
+				window->requeueLingoPlayState();
+			}
 			nextFrameNumberToLoad = ref.frameI;
 		} else {
 			if (debugChannelSet(-1, kDebugNoLoop)) {
diff --git a/engines/director/score.h b/engines/director/score.h
index 0e5a7c03d8c..56d3800998b 100644
--- a/engines/director/score.h
+++ b/engines/director/score.h
@@ -142,7 +142,6 @@ public:
 	void playSoundChannel(bool puppetOnly, bool sound1Changed = true, bool sound2Changed = true);
 
 	Common::String formatChannelInfo();
-	bool processFrozenPlayScript();
 
 	Common::MemoryReadStreamEndian *getSpriteDetailsStream(int spriteIdx);
 
diff --git a/engines/director/window.cpp b/engines/director/window.cpp
index fb588074cdb..ed062bd62e0 100644
--- a/engines/director/window.cpp
+++ b/engines/director/window.cpp
@@ -603,8 +603,8 @@ bool Window::step() {
 	if (_currentMovie && _currentMovie->getScore()->_playState == kPlayStopped) {
 		// attempt to thaw the lingo play state, if required
 		// For movie switches, we want to run it in the context of the new movie.
-		if (_nextMovie.movie.empty())
-			_currentMovie->getScore()->processFrozenPlayScript();
+		if (_nextMovie.movie.empty() && getLingoPlayState())
+			requeueLingoPlayState();
 		debugC(5, kDebugEvents, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
 		debugC(5, kDebugEvents, "@@@@   Finishing movie '%s' in '%s'", utf8ToPrintable(_currentMovie->getMacName()).c_str(), _currentPath.c_str());
 		debugC(5, kDebugEvents, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
@@ -760,6 +760,12 @@ void Window::thawLingoState() {
 	_frozenLingoStates.pop_back();
 }
 
+// When "play frame x" or "play frame movie" is called, something special happens.
+// The current Lingo state is frozen, in a different buffer to the normal frozen states.
+// That state is resumed if the Score play head reaches the end of the movie, or "play done"
+// is called. At that point, the current Lingo execution state is obliterated, and the
+// play state is introduced as the very first frozen state to process.
+
 void Window::freezeLingoPlayState() {
 	if (_lingoPlayState) {
 		warning("FIXME: Just clobbered the play state");
@@ -770,18 +776,12 @@ void Window::freezeLingoPlayState() {
 	debugC(3, kDebugLingoExec, "Freezing Lingo play state");
 }
 
-bool Window::thawLingoPlayState() {
+bool Window::requeueLingoPlayState() {
 	if (!_lingoPlayState) {
-		warning("Tried to thaw when there's no frozen play state, ignoring");
-		return false;
-	}
-	if (!_lingoState->callstack.empty()) {
-		warning("Can't thaw a Lingo state in mid-execution, ignoring");
+		warning("Tried to requeue when there's no frozen play state, ignoring");
 		return false;
 	}
-	delete _lingoState;
-	debugC(3, kDebugLingoExec, "Thawing Lingo play state");
-	_lingoState = _lingoPlayState;
+	_frozenLingoStates.insert_at(0, _lingoPlayState);
 	_lingoPlayState = nullptr;
 	return true;
 }
diff --git a/engines/director/window.h b/engines/director/window.h
index af371c39aae..6d2b025a8b5 100644
--- a/engines/director/window.h
+++ b/engines/director/window.h
@@ -177,7 +177,7 @@ public:
 	void freezeLingoState();
 	void thawLingoState();
 	void freezeLingoPlayState();
-	bool thawLingoPlayState();
+	bool requeueLingoPlayState();
 	LingoState *getLastFrozenLingoState() { return _frozenLingoStates.empty() ? nullptr : _frozenLingoStates[_frozenLingoStates.size() - 1]; }
 	void moveLingoState(Window *target);
 




More information about the Scummvm-git-logs mailing list