[Scummvm-git-logs] scummvm master -> 6a9d3bf4fb0073c878ed02a0592a8bed8bb4b15b

sev- noreply at scummvm.org
Fri Aug 7 20:37:29 UTC 2026


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

Summary:
43a0efc7aa DIRECTOR: XOBJ: Fix XObj stub for nikolaipharaohs
6565a821d7 GRAPHICS: MACGUI: Fix underrun in MacText::deletePreviousCharInternal
490b5ec654 DIRECTOR: LINGO: Keep tabs on active text widget
9a895fa75d DIRECTOR: Fix Window use-after-free bug
ba7fe724c3 DIRECTOR: XTRA: Add basic inline Smacker player
0eb003fa33 DIRECTOR: Add quirk system for b_open
6a9d3bf4fb DIRECTOR: LINGO: Add patch for Noir CD check


Commit: 43a0efc7aa9b4905f79c44fb0f1bc5f106bb86e2
    https://github.com/scummvm/scummvm/commit/43a0efc7aa9b4905f79c44fb0f1bc5f106bb86e2
Author: Scott Percival (code at moral.net.au)
Date: 2026-08-07T22:37:22+02:00

Commit Message:
DIRECTOR: XOBJ: Fix XObj stub for nikolaipharaohs

Changed paths:
    engines/director/lingo/xlibs/p/pharaohs.cpp


diff --git a/engines/director/lingo/xlibs/p/pharaohs.cpp b/engines/director/lingo/xlibs/p/pharaohs.cpp
index 22fa209209c..25dfc920af5 100644
--- a/engines/director/lingo/xlibs/p/pharaohs.cpp
+++ b/engines/director/lingo/xlibs/p/pharaohs.cpp
@@ -31,6 +31,7 @@
  *
  * USED IN:
  * gordak
+ * nikolaipharaohs
  *
  **************************************************/
 
@@ -100,7 +101,7 @@ XOBJSTUBNR(PharaohsXObj::m_dispose)
 XOBJSTUB(PharaohsXObj::m_windowsdir, "C:\\WINDOWS")
 XOBJSTUB(PharaohsXObj::m_writestring, 0)
 XOBJSTUB(PharaohsXObj::m_getstring, "")
-XOBJSTUB(PharaohsXObj::m_checkattrib, -1)
+XOBJSTUB(PharaohsXObj::m_checkattrib, 1)
 XOBJSTUB(PharaohsXObj::m_checkDrive, "D")
 
 }


Commit: 6565a821d7a01c77552ac92d59cddbaece13cd1d
    https://github.com/scummvm/scummvm/commit/6565a821d7a01c77552ac92d59cddbaece13cd1d
Author: Scott Percival (code at moral.net.au)
Date: 2026-08-07T22:37:22+02:00

Commit Message:
GRAPHICS: MACGUI: Fix underrun in MacText::deletePreviousCharInternal

Changed paths:
    graphics/macgui/mactext.cpp


diff --git a/graphics/macgui/mactext.cpp b/graphics/macgui/mactext.cpp
index 798fd469d76..cce12f20d78 100644
--- a/graphics/macgui/mactext.cpp
+++ b/graphics/macgui/mactext.cpp
@@ -2159,10 +2159,14 @@ void MacText::deletePreviousCharInternal(int *row, int *col) {
 		int pos = *col - 1;
 		uint ch = _canvas._text[*row].getChunkNum(&pos);
 
-		if (pos == (int)_canvas._text[*row].chunks[ch].text.size())
-			pos--;
+		int size = (int)_canvas._text[*row].chunks[ch].text.size();
 
-		_canvas._text[*row].chunks[ch].text.deleteChar(pos);
+		if (size > 0) {
+			if (pos == size)
+				pos--;
+
+			_canvas._text[*row].chunks[ch].text.deleteChar(pos);
+		}
 
 		(*col)--;
 	}


Commit: 490b5ec65464f33bae615b29d6dec82f49958214
    https://github.com/scummvm/scummvm/commit/490b5ec65464f33bae615b29d6dec82f49958214
Author: Scott Percival (code at moral.net.au)
Date: 2026-08-07T22:37:22+02:00

Commit Message:
DIRECTOR: LINGO: Keep tabs on active text widget

keyDown and keyUp events expect to be able to activate the cast/sprite
scripts associated with the current active text widget.

Fixes name entry screen in Nikolai's Pharaohs.

Changed paths:
    engines/director/channel.cpp
    engines/director/lingo/lingo-events.cpp
    engines/director/movie.h
    engines/director/score.cpp


diff --git a/engines/director/channel.cpp b/engines/director/channel.cpp
index 73a8e877666..7587340aea5 100644
--- a/engines/director/channel.cpp
+++ b/engines/director/channel.cpp
@@ -786,14 +786,26 @@ bool Channel::updateWidget() {
 		replaceWidget();
 		return true;
 	}
+
+	if (_sprite->_cast && (_sprite->_cast->_type == kCastText) && _sprite->_editable) {
+		if (_widget && _widget->_active) {
+			// small hack: update the castID/scriptID used for keyDown events.
+			// typing happens at the WM level, and this is one of the few places we can intercept it
+			Movie *movie = g_director->getCurrentMovie();
+			movie->_currentKeyDownCastID = _sprite->_castId;
+			movie->_currentKeyDownSpriteScriptID = _sprite->_scriptId;
+			movie->_currentKeyDownSpriteImmediate = _sprite->_immediate;
+		}
+	}
+
 	if (_widget && _widget->needsRedraw()) {
 		if (_sprite->_cast) {
 			_sprite->_cast->updateFromWidget(_widget, _sprite->_editable);
+
 		}
 		_widget->draw();
 		return true;
 	}
-
 	return false;
 }
 
diff --git a/engines/director/lingo/lingo-events.cpp b/engines/director/lingo/lingo-events.cpp
index 28bd6531dc5..f58d77664a8 100644
--- a/engines/director/lingo/lingo-events.cpp
+++ b/engines/director/lingo/lingo-events.cpp
@@ -259,6 +259,9 @@ void Movie::resolveScriptEvent(LingoEvent &event) {
 			if (((event.event == kEventMouseUp) || (event.event == kEventRightMouseUp)) && _vm->getVersion() < 400) {
 				scriptId = _currentMouseDownSpriteScriptID;
 				immediate = _currentMouseDownSpriteImmediate;
+			} else if (((event.event == kEventKeyDown) || (event.event == kEventKeyUp)) && _vm->getVersion() < 400) {
+				scriptId = _currentKeyDownSpriteScriptID;
+				immediate = _currentKeyDownSpriteImmediate;
 			} else {
 				// clickOn must reflect the release sprite so drop-target scripts
 				// can identify the channel
@@ -339,6 +342,8 @@ void Movie::resolveScriptEvent(LingoEvent &event) {
 			//
 			// mouseEnter and mouseLeave events should also defer to the value of channelId.
 			CastMemberID targetCast = _currentMouseDownCastID;
+			if ((event.event == kEventKeyUp) || (event.event == kEventKeyDown))
+				targetCast = _currentKeyDownCastID;
 			if ((event.event == kEventMouseDown) || (event.event == kEventRightMouseDown) ||
 				(event.event == kEventMouseEnter) || (event.event == kEventMouseLeave)) {
 				if (!event.channelId)
diff --git a/engines/director/movie.h b/engines/director/movie.h
index 8bdb260a111..a4d13e9e76a 100644
--- a/engines/director/movie.h
+++ b/engines/director/movie.h
@@ -165,6 +165,9 @@ public:
 	CastMemberID _currentMouseDownCastID;
 	CastMemberID _currentMouseDownSpriteScriptID;
 	bool _currentMouseDownSpriteImmediate;
+	CastMemberID _currentKeyDownCastID;
+	CastMemberID _currentKeyDownSpriteScriptID;
+	bool _currentKeyDownSpriteImmediate;
 	uint16 _currentEditableTextChannel;
 	uint32 _lastEventTime;
 	uint32 _lastRollTime;
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index 17e5069047c..f8040863507 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -969,6 +969,10 @@ void Score::updateSprites(RenderMode mode, bool withClean) {
 
 	_movie->_videoPlayback = false;
 
+	_movie->_currentKeyDownCastID = CastMemberID();
+	_movie->_currentKeyDownSpriteScriptID = CastMemberID();
+	_movie->_currentKeyDownSpriteImmediate = false;
+
 	for (uint16 i = 0; i < _channels.size(); i++) {
 		Channel *channel = _channels[i];
 		Sprite *currentSprite = channel->_sprite;


Commit: 9a895fa75db5e754f2147fbd0000567943dc496f
    https://github.com/scummvm/scummvm/commit/9a895fa75db5e754f2147fbd0000567943dc496f
Author: Scott Percival (code at moral.net.au)
Date: 2026-08-07T22:37:22+02:00

Commit Message:
DIRECTOR: Fix Window use-after-free bug

Changed paths:
    engines/director/director.cpp
    engines/director/events.cpp


diff --git a/engines/director/director.cpp b/engines/director/director.cpp
index e019289bc49..845b1727c90 100644
--- a/engines/director/director.cpp
+++ b/engines/director/director.cpp
@@ -237,10 +237,10 @@ bool DirectorEngine::isWindowRegistered(Window *window) const {
 void DirectorEngine::setCurrentWindow(Window *window) {
 	if (_currentWindow == window)
 		return;
+	window->incRefCount();
 	if (_currentWindow)
 		_currentWindow->decRefCount();
 	_currentWindow = window;
-	_currentWindow->incRefCount();
 }
 
 void DirectorEngine::setVersion(uint16 version) {
diff --git a/engines/director/events.cpp b/engines/director/events.cpp
index 2cf1cc73b61..f8437569138 100644
--- a/engines/director/events.cpp
+++ b/engines/director/events.cpp
@@ -138,6 +138,7 @@ bool Window::processSysEvent(Common::Event &event) {
 
 	if (g_lingo) {
 		Window *prev = g_director->getCurrentWindow();
+		prev->incRefCount();
 		g_director->setCurrentWindow(this);
 		g_lingo->switchStateFromWindow();
 
@@ -145,6 +146,7 @@ bool Window::processSysEvent(Common::Event &event) {
 			flag = true;
 
 		g_director->setCurrentWindow(prev);
+		prev->decRefCount();
 		g_lingo->switchStateFromWindow();
 	}
 


Commit: ba7fe724c30bab9bbb7fd654df89933efd44b8dc
    https://github.com/scummvm/scummvm/commit/ba7fe724c30bab9bbb7fd654df89933efd44b8dc
Author: Scott Percival (code at moral.net.au)
Date: 2026-08-07T22:37:22+02:00

Commit Message:
DIRECTOR: XTRA: Add basic inline Smacker player

Changed paths:
    engines/director/lingo/xtras/s/smacker.cpp
    engines/director/lingo/xtras/s/smacker.h


diff --git a/engines/director/lingo/xtras/s/smacker.cpp b/engines/director/lingo/xtras/s/smacker.cpp
index 2c2642a941c..c81c92bfe3d 100644
--- a/engines/director/lingo/xtras/s/smacker.cpp
+++ b/engines/director/lingo/xtras/s/smacker.cpp
@@ -20,8 +20,10 @@
  */
 
 #include "common/system.h"
+#include "video/smk_decoder.h"
 
 #include "director/director.h"
+#include "director/images.h"
 #include "director/lingo/lingo.h"
 #include "director/lingo/lingo-object.h"
 #include "director/lingo/lingo-utils.h"
@@ -169,6 +171,90 @@ void SmackerXtra::m_new(int nargs) {
 	g_lingo->push(g_lingo->_state->me);
 }
 
+void SmackerXtra::playSmacker(const Common::String &videoPath, const Common::Rect &bbox, bool usePalette) {
+	Video::SmackerDecoder *video = new Video::SmackerDecoder();
+	bool result = video->loadFile(findPath(videoPath));
+	if (!result) {
+		warning("playSmacker: Smacker video not loaded: %s", videoPath.c_str());
+		delete video;
+		return;
+	}
+
+	// save the current palette
+	byte origPalette[256 * 3];
+	uint16 origCount = g_director->getPaletteColorCount();
+
+	if (origCount > 256) {
+		warning("playSmacker: too big palette, %d > 256", origCount);
+		origCount = 256;
+	}
+
+	memcpy(origPalette, g_director->getPalette(), origCount * 3);
+	byte videoPalette[256 * 3];
+
+	Graphics::Surface const *frame = nullptr;
+	Graphics::ManagedSurface *dest = new Graphics::ManagedSurface(bbox.width(), bbox.height(), g_director->_pixelformat);
+	Common::Event event;
+	bool keepPlaying = true;
+	video->start();
+	memcpy(videoPalette, origPalette, 256 * 3);
+	while (!video->endOfVideo()) {
+		if (g_director->pollEvent(event)) {
+			switch (event.type) {
+				case Common::EVENT_QUIT:
+					g_director->processEventQUIT();
+					// fallthrough
+				case Common::EVENT_KEYDOWN:
+				case Common::EVENT_RBUTTONDOWN:
+				case Common::EVENT_LBUTTONDOWN:
+					keepPlaying = false;
+					break;
+				default:
+					break;
+			}
+		}
+		if (!keepPlaying)
+			break;
+		if (video->needsUpdate()) {
+			frame = video->decodeNextFrame();
+			// Palette info gets set after the frame is decoded
+			if (video->hasDirtyPalette()) {
+				byte *palette = const_cast<byte *>(video->getPalette());
+				memcpy(videoPalette, palette, 256 * 3);
+				if (usePalette)
+					g_director->setPalette(videoPalette, 256);
+			}
+
+			copyStretchImg(
+				frame,
+				dest->surfacePtr(),
+				frame->getRect(),
+				dest->getBounds(),
+				videoPalette
+			);
+			g_system->copyRectToScreen(dest->getPixels(), dest->pitch, bbox.left, bbox.top, bbox.width(), bbox.height());
+
+			// Video palette order is going to be different to the screen, we need to untangle it
+			/*Graphics::Surface *dither = frame->convertTo(g_director->_wm->_pixelformat, videoPalette, 256,
+					usePalette ? videoPalette : origPalette, usePalette ? 256 : origCount, Graphics::kDitherNaive);
+			int width = MIN(dither->w + posX, (int)g_system->getWidth()) - bbox.left;
+			int height = MIN(dither->h + posY, (int)g_system->getHeight()) - bbox.top;
+			g_system->copyRectToScreen(dither->getPixels(), dither->pitch, bbox.left, bbox.top, width, height);
+			dither->free();
+			delete dither;*/
+		}
+		g_system->updateScreen();
+		g_director->delayMillis(10);
+	}
+
+	video->close();
+	delete video;
+	delete dest;
+	g_director->setPalette(origPalette, 256);
+
+}
+
+
 XOBJSTUB(SmackerXtra::m_SmackQuickPlay, 0)
 XOBJSTUB(SmackerXtra::m_SmackQuickPlayTrans, 0)
 XOBJSTUB(SmackerXtra::m_SmackOpen, 0)
diff --git a/engines/director/lingo/xtras/s/smacker.h b/engines/director/lingo/xtras/s/smacker.h
index 635c26a2491..9f8ffe85f3a 100644
--- a/engines/director/lingo/xtras/s/smacker.h
+++ b/engines/director/lingo/xtras/s/smacker.h
@@ -76,6 +76,8 @@ void m_SmackSetDisplayMode(int nargs);
 void m_SmackGetSummary(int nargs);
 void m_SmackScreenMethod(int nargs);
 
+void playSmacker(const Common::String &videoPath, const Common::Rect &bbox, bool usePalette);
+
 } // End of namespace SmackerXtra
 
 } // End of namespace Director


Commit: 0eb003fa3320b5c358144c682ce8283615d697af
    https://github.com/scummvm/scummvm/commit/0eb003fa3320b5c358144c682ce8283615d697af
Author: Scott Percival (code at moral.net.au)
Date: 2026-08-07T22:37:22+02:00

Commit Message:
DIRECTOR: Add quirk system for b_open

Director can open any program on the system it wants. For obvious
reasons, we can't do that here, but we can allow overrides into native
code. In the case of Noir, there is an external player EXE which plays a
Smacker video in the foreground, then returns control to the game.

Fixes introductory movies in Noir: A Shadowy Thriller

Changed paths:
    engines/director/director.h
    engines/director/game-quirks.cpp
    engines/director/lingo/lingo-builtins.cpp


diff --git a/engines/director/director.h b/engines/director/director.h
index 0e88a615881..a454b81dcb4 100644
--- a/engines/director/director.h
+++ b/engines/director/director.h
@@ -252,6 +252,7 @@ public:
 	Common::Array<Common::Event> _injectedEvents;
 
 	// game-quirks.cpp
+	bool lingoOpenWrapper(const char *target, Common::Platform platform, const Common::String &whichApplication, const Common::String &whichDocument);
 	void gameQuirks(const char *target, Common::Platform platform);
 	void loadSlowdownCooloff(uint32 delay = 2000);
 
diff --git a/engines/director/game-quirks.cpp b/engines/director/game-quirks.cpp
index 413105d5a9e..4ed09bbf540 100644
--- a/engines/director/game-quirks.cpp
+++ b/engines/director/game-quirks.cpp
@@ -24,7 +24,11 @@
 #include "common/memstream.h"
 #include "common/platform.h"
 #include "common/savefile.h"
+#include "common/tokenizer.h"
 #include "director/director.h"
+#include "director/movie.h"
+#include "director/lingo/lingo-object.h"
+#include "director/lingo/xtras/s/smacker.h"
 
 namespace Director {
 
@@ -186,6 +190,33 @@ struct SaveFilePath {
 	{ nullptr, Common::kPlatformUnknown, nullptr },
 };
 
+static void quirkSmacker(const Common::String &whichDocument) {
+	Common::StringTokenizer tok(whichDocument);
+	Common::String videoFile = tok.nextToken();
+	SmackerXtra::playSmacker(videoFile, g_director->getCurrentMovie()->_movieRect, true);
+}
+
+struct LingoOpenWrapper {
+	const char *target;
+	Common::Platform platform;
+	const char *application;
+	void (*quirk)(const Common::String &whichDocument);
+} const lingoOpenWrappers[] = {
+	{"noir", Common::kPlatformWindows, "C:\\SPLAY", quirkSmacker },
+	{ nullptr, Common::kPlatformUnknown, nullptr, nullptr }
+};
+
+bool DirectorEngine::lingoOpenWrapper(const char *target, Common::Platform platform, const Common::String &whichApplication, const Common::String &whichDocument) {
+	for (auto q = lingoOpenWrappers; q->target != nullptr; q++) {
+		if (q->platform == Common::kPlatformUnknown || q->platform == platform)
+			if (!strcmp(q->target, target) && whichApplication.equalsIgnoreCase(q->application)) {
+				q->quirk(whichDocument);
+				return true;
+				break;
+			}
+	}
+	return false;
+}
 
 static void quirkWarlock() {
 	g_director->_loadSlowdownFactor = 150000;  // emulate a 1x CD drive
diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp
index 23ff5e6c365..10925e4c0a0 100644
--- a/engines/director/lingo/lingo-builtins.cpp
+++ b/engines/director/lingo/lingo-builtins.cpp
@@ -1715,8 +1715,13 @@ void LB::b_open(int nargs) {
 
 	Datum ex = g_lingo->pop();
 	Datum d;
+
 	if (nargs == 2)
 		d = g_lingo->pop();
+
+	if (g_director->lingoOpenWrapper(g_director->getTargetName().c_str(), g_director->getPlatform(), ex.asString(), d.asString()))
+		return;
+
 	warning("LB::b_open(): Unsupported command open encountered -> The movie tried to open %s %s", ex.asString().c_str(), d.type != VOID ? d.asString().c_str() : "");
 
 	if (!debugChannelSet(-1, kDebugFewFramesOnly) &&


Commit: 6a9d3bf4fb0073c878ed02a0592a8bed8bb4b15b
    https://github.com/scummvm/scummvm/commit/6a9d3bf4fb0073c878ed02a0592a8bed8bb4b15b
Author: Scott Percival (code at moral.net.au)
Date: 2026-08-07T22:37:22+02:00

Commit Message:
DIRECTOR: LINGO: Add patch for Noir CD check

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


diff --git a/engines/director/lingo/lingo-patcher.cpp b/engines/director/lingo/lingo-patcher.cpp
index 0bd3f0741e0..2710dffd4b0 100644
--- a/engines/director/lingo/lingo-patcher.cpp
+++ b/engines/director/lingo/lingo-patcher.cpp
@@ -557,6 +557,16 @@ macro InventoryArrowsClicked\r\
   updateStage\r\
 ";
 
+/*
+ * Noir: A Shadowy Thriller checks a file to determine if CD 1 or 2 is inserted.
+ * We assume all the files are in the same path.
+ */
+const char *const noirCDCheck = "\
+on hCorrectCD lNeedCD 	\r\
+   return 1 \r\
+end \r\
+";
+
 struct ScriptHandlerPatch {
 	const char *gameId;
 	const char *extra;
@@ -603,6 +613,7 @@ struct ScriptHandlerPatch {
 	{"gordak", nullptr, kPlatformWindows, "GORDAKCD.EXE", kMovieScript, 2, DEFAULT_CAST_LIB, &gordakDetectionFix},
 	{"getaheadmath", nullptr, kPlatformWindows, "HDFILES\\CHANNELS\\BASE.CST", kParentScript, 69, 2, &getaheadmathDiskFix},
 	{"jman", "v1.2", kPlatformMacintosh, "Support Files:Mars ESG Upper 03", kMovieScript, 322, DEFAULT_CAST_LIB, &jmanInventory},
+	{"noir", nullptr, kPlatformWindows, "SHARED.CST", kMovieScript, 9, 2, &noirCDCheck},
 	{nullptr, nullptr, kPlatformUnknown, nullptr, kNoneScript, 0, 0, nullptr},
 
 };




More information about the Scummvm-git-logs mailing list