[Scummvm-git-logs] scummvm master -> 63c791aeda61eca89cb9e47c517e645a0b5e713f

sev- noreply at scummvm.org
Sat Jul 8 19:01:13 UTC 2023


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

Summary:
97180fec5c DIRECTOR: Add search paths to 'info' debugger command
4dbfd5b1e7 DIRECTOR: Ignore directory field when loading cast members from files
862a79aa06 DIRECTOR: Add stub for Consumer XObj
353b843803 DIRECTOR: Implement b_param
bd436cd231 DIRECTOR: Fix getObjectProp if cast member doesn't exist
17e1853347 DIRECTOR: Refactor path detection
2690a238ce DIRECTOR: Add extensions if required when loading audio files
0bcf80e400 DIRECTOR: Fix detection entry for planetarizona
1a84d4d94b DIRECTOR: XOBJ: Add stub for DirUtil
343e3af14f DIRECTOR: Change Datum::asInt/asFloat behaviour for strings
d7b779f085 DIRECTOR: Add devtool for generating XObject/XLib stubs
34d962d959 DIRECTOR: Add Gothos to game list
4be2399b6b DIRECTOR: LINGO: Make scummvmAssert crash only with kDebugLingoStrict
17e43beb3d DIRECTOR: Add alias for RearWindow XObj
3245ae99fa DIRECTOR: Rework resolvePath functions to pass Path instead of FSNode
0d43130eda DIRECTOR: Fix libId and getHandler for multi-cast support
1253fc9e9e DIRECTOR: LINGO: Fix bytecode crashes for D5
5cc9a5b593 DIRECTOR: Fix funcs debugger command to list from all casts
93bd0741d6 DIRECTOR: Interrogate SearchMan for path matches
8a6d7d6c95 DIRECTOR: Move file extension checks to innermost loop
8555abd76a DIRECTOR: XOBJ: Add another alias for FlushXObj and FixPaletteXObj
70ffd87c62 DIRECTOR: Add guardrails to loadSampleSounds
aa32a51c47 DIRECTOR: More fixes for partial filename matching
36cb4594c2 DIRECTOR: Fix ProjectorArchive path resolution
63c791aeda DIRECTOR: Don't skip frame delay when switching frames


Commit: 97180fec5c04ba1e3fcffe884aa3f4c94e2c17d0
    https://github.com/scummvm/scummvm/commit/97180fec5c04ba1e3fcffe884aa3f4c94e2c17d0
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Add search paths to 'info' debugger command

Changed paths:
    engines/director/debugger.cpp


diff --git a/engines/director/debugger.cpp b/engines/director/debugger.cpp
index 7ccab5bad59..d2ddd26ed6a 100644
--- a/engines/director/debugger.cpp
+++ b/engines/director/debugger.cpp
@@ -232,6 +232,14 @@ bool Debugger::cmdInfo(int argc, const char **argv) {
 	debugPrintf("Allow outdated Lingo flag: %d\n", movie->_allowOutdatedLingo);
 	debugPrintf("Frame count: %d\n", score->_frames.size());
 	debugPrintf("Cast member count: %d\n", cast->getCastSize());
+	debugPrintf("Search paths:\n");
+	if (g_lingo->_searchPath.isArray() && g_lingo->_searchPath.u.farr->arr.size() > 0) {
+		for (auto &it : g_lingo->_searchPath.u.farr->arr) {
+			debugPrintf("    %s\n", it.asString().c_str());
+		}
+	} else {
+		debugPrintf("    [empty]\n");
+	}
 	debugPrintf("\n");
 	return true;
 }


Commit: 4dbfd5b1e7c022c6485d38142b4377a352c4f7f0
    https://github.com/scummvm/scummvm/commit/4dbfd5b1e7c022c6485d38142b4377a352c4f7f0
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Ignore directory field when loading cast members from files

For linked cast members, the CastMemberInfo struct has a file name and a
directory. The directory is taken from the development environment used
to make the movie, and can be unrelated to the final directory
structure. In practice, the filename is checked against the list of
search paths.

Fixes all of the external media in Bob Winkle Solves Life's Mysteries,
and the exit screen music in Eastern Mind.

Changed paths:
    engines/director/castmember/bitmap.cpp
    engines/director/castmember/sound.cpp


diff --git a/engines/director/castmember/bitmap.cpp b/engines/director/castmember/bitmap.cpp
index 6640bd021e5..62200d5a34c 100644
--- a/engines/director/castmember/bitmap.cpp
+++ b/engines/director/castmember/bitmap.cpp
@@ -518,11 +518,7 @@ void BitmapCastMember::load() {
 		if ((pic == nullptr || pic->size() == 0)
 				&& ci && !ci->fileName.empty()) {
 			// image file is linked, load from the filesystem
-			Common::String filename = ci->fileName;
-			Common::String directory = ci->directory;
-
-			Common::String imageFilename = directory + g_director->_dirSeparator + filename;
-
+			Common::String imageFilename = ci->fileName;
 			Common::Path path = Common::Path(pathMakeRelative(imageFilename), g_director->_dirSeparator);
 
 			Common::SeekableReadStream *file = Common::MacResManager::openFileOrDataFork(path);
diff --git a/engines/director/castmember/sound.cpp b/engines/director/castmember/sound.cpp
index afbb4b8108e..f851a31de04 100644
--- a/engines/director/castmember/sound.cpp
+++ b/engines/director/castmember/sound.cpp
@@ -79,9 +79,6 @@ void SoundCastMember::load() {
 		if (ci) {
 			Common::String filename = ci->fileName;
 
-			if (!ci->directory.empty())
-				filename = ci->directory + g_director->_dirSeparator + ci->fileName;
-
 			debugC(2, kDebugLoading, "****** Loading file '%s', cast id: %d", filename.c_str(), sndId);
 			AudioFileDecoder *audio = new AudioFileDecoder(filename);
 			_audio = audio;


Commit: 862a79aa069293ca70c5b9b9bfe7ca7efec667d8
    https://github.com/scummvm/scummvm/commit/862a79aa069293ca70c5b9b9bfe7ca7efec667d8
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Add stub for Consumer XObj

Changed paths:
  A engines/director/lingo/xlibs/consumer.cpp
  A engines/director/lingo/xlibs/consumer.h
    engines/director/lingo/lingo-object.cpp
    engines/director/module.mk


diff --git a/engines/director/lingo/lingo-object.cpp b/engines/director/lingo/lingo-object.cpp
index a6b63673b2c..9d92241363a 100644
--- a/engines/director/lingo/lingo-object.cpp
+++ b/engines/director/lingo/lingo-object.cpp
@@ -40,6 +40,7 @@
 #include "director/lingo/xlibs/blitpict.h"
 #include "director/lingo/xlibs/cdromxobj.h"
 #include "director/lingo/xlibs/colorxobj.h"
+#include "director/lingo/xlibs/consumer.h"
 #include "director/lingo/xlibs/darkenscreen.h"
 #include "director/lingo/xlibs/developerStack.h"
 #include "director/lingo/xlibs/dialogsxobj.h"
@@ -168,6 +169,7 @@ static struct XLibProto {
 	{ BlitPict::fileNames,				BlitPict::open,				BlitPict::close,			kXObj,					400 },	// D4
 	{ CDROMXObj::fileNames,				CDROMXObj::open,			CDROMXObj::close,			kXObj,					200 },	// D2
 	{ ColorXObj::fileNames,				ColorXObj::open,			ColorXObj::close,			kXObj,					400 },	// D4
+	{ ConsumerXObj::fileNames,			ConsumerXObj::open,			ConsumerXObj::close,		kXObj,					400 },	// D4
 	{ DarkenScreen::fileNames,			DarkenScreen::open,			DarkenScreen::close,		kXObj,					300 },	// D3
 	{ DeveloperStack::fileNames,		DeveloperStack::open,		DeveloperStack::close,		kXObj,					300 },	// D3
 	{ DialogsXObj::fileNames,			DialogsXObj::open,			DialogsXObj::close,			kXObj,					400 },	// D4
diff --git a/engines/director/lingo/xlibs/consumer.cpp b/engines/director/lingo/xlibs/consumer.cpp
new file mode 100644
index 00000000000..c84a378cdf6
--- /dev/null
+++ b/engines/director/lingo/xlibs/consumer.cpp
@@ -0,0 +1,128 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/system.h"
+
+#include "director/director.h"
+#include "director/lingo/lingo.h"
+#include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
+#include "director/lingo/xlibs/consumer.h"
+
+/*
+ * Used in: Bob Winkle Solves Life's Greatest Mysteries
+ *
+-- Consumer dialog xobject. 02Aug95 JA
+Consumer
+I      mNew                --Creates a new instance of the XObject
+X      mDispose            --Disposes of XObject instance
+S      mName               --Returns the XObject name (Consumer)
+SII        mSettingsDlg            --Shows the settings dialog and returns results
+ISIS       mPopHotword         --Pops up a hot word window
+SSSSS      mExplore                        --Shows the Eplore dialog
+SSSS   mPrefSetValue               --Set a preference file entry
+SSS    mPrefGetValue       --Get a preference file entry
+SSSSSS mContentLock                --Shows the content lock dialog
+SSI        mGetRecord                      --Retrieves a data file record
+SSS        mGetWinIniEntry         --Retrieves an entry from the WIN.INI
+I      mAboutDlg                   --Displays the About Box
+IS     mRegisterDlg                --Displays the Registration Dialog
+I      mLockedDlg                  --Displays the Term Locked Dialog
+II         mCheckCD                        --Checks for the presence of the CD
+I          mHackMenu                       --Hack to destroy menu
+ */
+
+namespace Director {
+
+const char *ConsumerXObj::xlibName = "consumer";
+const char *ConsumerXObj::fileNames[] = {
+	"consumer",
+	nullptr
+};
+
+static MethodProto xlibMethods[] = {
+	{ "new",					ConsumerXObj::m_new,			 0, 0,	400 },	// D4
+	{ "dispose",				ConsumerXObj::m_dispose,		 0, 0,	400 },	// D4
+	{ "name",					ConsumerXObj::m_name,			 0, 0,	400 },	// D4
+	{ "settingsDlg",			ConsumerXObj::m_settingsDlg,	 2, 2,	400 },	// D4
+	{ "popHotword",				ConsumerXObj::m_popHotword,		 3, 3,	400 },	// D4
+	{ "explore",				ConsumerXObj::m_explore,		 4, 4,	400 },	// D4
+	{ "prefSetValue",			ConsumerXObj::m_prefSetValue,	 3, 3,	400 },	// D4
+	{ "prefGetValue",			ConsumerXObj::m_prefGetValue,	 2, 2,	400 },	// D4
+	{ "contentLock",			ConsumerXObj::m_contentLock,	 5, 5,	400 },	// D4
+	{ "getRecord",				ConsumerXObj::m_getRecord,		 2, 2,	400 },	// D4
+	{ "getWinIniEntry",			ConsumerXObj::m_getWinIniEntry,	 2, 2,	400 },	// D4
+	{ "aboutDlg",				ConsumerXObj::m_aboutDlg,		 0, 0,	400 },	// D4
+	{ "registerDlg",			ConsumerXObj::m_registerDlg,	 1, 1,	400 },	// D4
+	{ "lockedDlg",				ConsumerXObj::m_lockedDlg,		 0, 0,	400 },	// D4
+	{ "checkCD",				ConsumerXObj::m_checkCD,		 1, 1,	400 },	// D4
+	{ "hackMenu",				ConsumerXObj::m_hackMenu,		 0, 0,	400 },	// D4
+	{ nullptr, nullptr, 0, 0, 0 }
+};
+
+ConsumerXObject::ConsumerXObject(ObjectType ObjectType) :Object<ConsumerXObject>("ConsumerXObj") {
+	_objType = ObjectType;
+}
+
+void ConsumerXObj::open(int type) {
+	if (type == kXObj) {
+		ConsumerXObject::initMethods(xlibMethods);
+		ConsumerXObject *xobj = new ConsumerXObject(kXObj);
+		g_lingo->exposeXObject(xlibName, xobj);
+	} else if (type == kXtraObj) {
+		// TODO - Implement Xtra
+	}
+}
+
+void ConsumerXObj::close(int type) {
+	if (type == kXObj) {
+		ConsumerXObject::cleanupMethods();
+		g_lingo->_globalvars[xlibName] = Datum();
+	} else if (type == kXtraObj) {
+		// TODO - Implement Xtra
+	}
+}
+
+void ConsumerXObj::m_new(int nargs) {
+	if (nargs != 0) {
+		warning("Consumer::m_new: expected 0 arguments");
+		g_lingo->dropStack(nargs);
+	}
+	g_lingo->push(g_lingo->_state->me);
+}
+
+XOBJSTUBNR(ConsumerXObj::m_dispose)
+XOBJSTUB(ConsumerXObj::m_name, "Consumer")
+XOBJSTUB(ConsumerXObj::m_settingsDlg, "")
+XOBJSTUB(ConsumerXObj::m_popHotword, 0)
+XOBJSTUB(ConsumerXObj::m_explore, "")
+XOBJSTUB(ConsumerXObj::m_prefSetValue, "")
+XOBJSTUB(ConsumerXObj::m_prefGetValue, "")
+XOBJSTUB(ConsumerXObj::m_contentLock, "")
+XOBJSTUB(ConsumerXObj::m_getRecord, "0,0,0") // used to bypass the locked question check
+XOBJSTUB(ConsumerXObj::m_getWinIniEntry, "")
+XOBJSTUB(ConsumerXObj::m_aboutDlg, 0)
+XOBJSTUB(ConsumerXObj::m_registerDlg, 0)
+XOBJSTUB(ConsumerXObj::m_lockedDlg, 0)
+XOBJSTUB(ConsumerXObj::m_checkCD, 1)
+XOBJSTUB(ConsumerXObj::m_hackMenu, 0)
+
+}
diff --git a/engines/director/lingo/xlibs/consumer.h b/engines/director/lingo/xlibs/consumer.h
new file mode 100644
index 00000000000..513fbbb95ce
--- /dev/null
+++ b/engines/director/lingo/xlibs/consumer.h
@@ -0,0 +1,61 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef DIRECTOR_LINGO_XLIBS_CONSUMER_H
+#define DIRECTOR_LINGO_XLIBS_CONSUMER_H
+
+namespace Director {
+
+class ConsumerXObject : public Object<ConsumerXObject> {
+public:
+	ConsumerXObject(ObjectType objType);
+};
+
+namespace ConsumerXObj {
+
+extern const char *xlibName;
+extern const char *fileNames[];
+
+void open(int type);
+void close(int type);
+
+void m_new(int nargs);
+void m_dispose(int nargs);
+void m_name(int nargs);
+void m_settingsDlg(int nargs);
+void m_popHotword(int nargs);
+void m_explore(int nargs);
+void m_prefSetValue(int nargs);
+void m_prefGetValue(int nargs);
+void m_contentLock(int nargs);
+void m_getRecord(int nargs);
+void m_getWinIniEntry(int nargs);
+void m_aboutDlg(int nargs);
+void m_registerDlg(int nargs);
+void m_lockedDlg(int nargs);
+void m_checkCD(int nargs);
+void m_hackMenu(int nargs);
+
+} // End of namespace ConsumerXObj
+
+} // End of namespace Director
+
+#endif
diff --git a/engines/director/module.mk b/engines/director/module.mk
index 8d00d32c5f5..0300bc890e0 100644
--- a/engines/director/module.mk
+++ b/engines/director/module.mk
@@ -60,6 +60,7 @@ MODULE_OBJS = \
 	lingo/xlibs/blitpict.o \
 	lingo/xlibs/cdromxobj.o \
 	lingo/xlibs/colorxobj.o \
+	lingo/xlibs/consumer.o \
 	lingo/xlibs/darkenscreen.o \
 	lingo/xlibs/developerStack.o \
 	lingo/xlibs/dialogsxobj.o \


Commit: 353b843803ddece3f0f9f4cd1cb4e0a97b0bfa1c
    https://github.com/scummvm/scummvm/commit/353b843803ddece3f0f9f4cd1cb4e0a97b0bfa1c
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Implement b_param

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


diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp
index 3c9234348db..c6c9beffbc7 100644
--- a/engines/director/lingo/lingo-builtins.cpp
+++ b/engines/director/lingo/lingo-builtins.cpp
@@ -23,6 +23,7 @@
 #include "common/tokenizer.h"
 #include "common/translation.h"
 
+#include "director/types.h"
 #include "gui/message.h"
 
 #include "graphics/macgui/macwindowmanager.h"
@@ -1660,9 +1661,17 @@ void LB::b_HMStoFrames(int nargs) {
 }
 
 void LB::b_param(int nargs) {
-	g_lingo->printSTUBWithArglist("b_param", nargs);
-
-	g_lingo->dropStack(nargs);
+	int pos = g_lingo->pop().asInt();
+	Datum result;
+	CFrame *cf = g_lingo->_state->callstack[g_lingo->_state->callstack.size() - 1];
+	if (pos > 0 && cf->sp.argNames && (int)cf->sp.argNames->size() <= pos) {
+		Datum func((*cf->sp.argNames)[pos - 1]);
+		func.type = LOCALREF;
+		result = g_lingo->varFetch(func);
+	} else {
+		warning("Invalid argument position %d", pos);
+	}
+	g_lingo->push(result);
 }
 
 void LB::b_printFrom(int nargs) {


Commit: bd436cd231af8772430535521f9890e7e87ec01d
    https://github.com/scummvm/scummvm/commit/bd436cd231af8772430535521f9890e7e87ec01d
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Fix getObjectProp if cast member doesn't exist

This operation should only be a hard fail if the cast member ID is out
of bounds, otherwise it should return VOID.

Fixes Motion Sickness chapter of Bob Winkle Solves Life's Greatest
Mysteries.

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


diff --git a/engines/director/lingo/lingo-the.cpp b/engines/director/lingo/lingo-the.cpp
index e9aaf3f85a4..f93939b2c9a 100644
--- a/engines/director/lingo/lingo-the.cpp
+++ b/engines/director/lingo/lingo-the.cpp
@@ -1941,10 +1941,12 @@ void Lingo::getObjectProp(Datum &obj, Common::String &propName) {
 		if (!member) {
 			if (propName.equalsIgnoreCase("loaded")) {
 				d = 0;
-			} else  if (propName.equalsIgnoreCase("filename")) {
+			} else if (propName.equalsIgnoreCase("filename")) {
 				d = Datum(Common::String());
+			} else if (id.member <= getMembersNum()) {
+				warning("Lingo::getObjectProp(): %s not found", id.asString().c_str());
 			} else {
-				g_lingo->lingoError("Lingo::getObjectProp(): %s not found", id.asString().c_str());
+				g_lingo->lingoError("Lingo::getObjectProp(): %s not found and out of range", id.asString().c_str());
 			}
 			g_lingo->push(d);
 			return;


Commit: 17e1853347368f2f5aaee0e188ec30f54cc8a203
    https://github.com/scummvm/scummvm/commit/17e1853347368f2f5aaee0e188ec30f54cc8a203
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Refactor path detection

Previously, paths were verified by pathMakeRelative().
This method had several drawbacks; the code was increasingly ad-hoc
and recursive, and the function would return a Common::String that
was not guaranteed to be distinct. 90% of the time the path would need
to be converted to Common::Path so that file streams could use it.

This code has been replaced with findPath(), a function which takes a
path string as recieved from Director (e.g. a Lingo script, a filename
stored in a CastInfo record) and returns a Common::Path object for the
file on the local system. The inner workings have been split into subfunctions,
which should make it easier to add workarounds and fuzz at the correct
layer.

Changed paths:
    engines/director/archive.cpp
    engines/director/archive.h
    engines/director/castmember/bitmap.cpp
    engines/director/castmember/digitalvideo.cpp
    engines/director/castmember/sound.cpp
    engines/director/channel.cpp
    engines/director/director.h
    engines/director/lingo/lingo-builtins.cpp
    engines/director/lingo/lingo-the.cpp
    engines/director/lingo/xlibs/aiff.cpp
    engines/director/lingo/xlibs/fileexists.cpp
    engines/director/lingo/xlibs/fileio.cpp
    engines/director/movie.cpp
    engines/director/movie.h
    engines/director/resource.cpp
    engines/director/sound.cpp
    engines/director/tests.cpp
    engines/director/util.cpp
    engines/director/util.h
    engines/director/window.cpp
    engines/director/window.h


diff --git a/engines/director/archive.cpp b/engines/director/archive.cpp
index 8c43c0962bd..2417fad7893 100644
--- a/engines/director/archive.cpp
+++ b/engines/director/archive.cpp
@@ -46,19 +46,19 @@ Archive::~Archive() {
 
 Common::String Archive::getFileName() const { return Director::getFileName(_pathName); }
 
-bool Archive::openFile(const Common::String &fileName) {
+bool Archive::openFile(const Common::Path &path) {
 	Common::File *file = new Common::File();
 
-	if (!file->open(Common::Path(fileName, g_director->_dirSeparator))) {
-		warning("Archive::openFile(): Error opening file %s", fileName.c_str());
+	if (path.empty() || !file->open(path)) {
+		warning("Archive::openFile(): Error opening file %s", path.toString().c_str());
 		delete file;
 		return false;
 	}
 
-	_pathName = fileName;
+	_pathName = path.toString(g_director->_dirSeparator);
 
 	if (!openStream(file)) {
-		warning("Archive::openFile(): Error loading stream from file %s", fileName.c_str());
+		warning("Archive::openFile(): Error loading stream from file %s", path.toString().c_str());
 		close();
 		return false;
 	}
@@ -309,14 +309,12 @@ void MacArchive::close() {
 	_resFork = nullptr;
 }
 
-bool MacArchive::openFile(const Common::String &fileName) {
+bool MacArchive::openFile(const Common::Path &path) {
 	close();
 
 	_resFork = new Common::MacResManager();
 
-	Common::String fName = fileName;
-
-	if (!_resFork->open(Common::Path(fName, g_director->_dirSeparator)) || !_resFork->hasResFork()) {
+	if (path.empty() || !_resFork->open(path) || !_resFork->hasResFork()) {
 		close();
 		return false;
 	}
diff --git a/engines/director/archive.h b/engines/director/archive.h
index cbdc988e0a2..e1622d8a9d0 100644
--- a/engines/director/archive.h
+++ b/engines/director/archive.h
@@ -27,6 +27,7 @@ class MacResManager;
 class SeekableMemoryWriteStream;
 class SeekableReadStreamEndian;
 class SeekableReadStream;
+class Path;
 }
 
 namespace Director {
@@ -52,7 +53,7 @@ public:
 	Archive();
 	virtual ~Archive();
 
-	virtual bool openFile(const Common::String &fileName);
+	virtual bool openFile(const Common::Path &path);
 	virtual bool openStream(Common::SeekableReadStream *stream, uint32 offset = 0) = 0;
 	virtual void close();
 
@@ -100,7 +101,7 @@ public:
 	~MacArchive() override;
 
 	void close() override;
-	bool openFile(const Common::String &fileName) override;
+	bool openFile(const Common::Path &path) override;
 	bool openStream(Common::SeekableReadStream *stream, uint32 startOffset = 0) override;
 	Common::SeekableReadStreamEndian *getResource(uint32 tag, uint16 id) override;
 	Common::String formatArchiveInfo() override;
diff --git a/engines/director/castmember/bitmap.cpp b/engines/director/castmember/bitmap.cpp
index 62200d5a34c..a2ef7cbd583 100644
--- a/engines/director/castmember/bitmap.cpp
+++ b/engines/director/castmember/bitmap.cpp
@@ -518,10 +518,9 @@ void BitmapCastMember::load() {
 		if ((pic == nullptr || pic->size() == 0)
 				&& ci && !ci->fileName.empty()) {
 			// image file is linked, load from the filesystem
-			Common::String imageFilename = ci->fileName;
-			Common::Path path = Common::Path(pathMakeRelative(imageFilename), g_director->_dirSeparator);
-
-			Common::SeekableReadStream *file = Common::MacResManager::openFileOrDataFork(path);
+			Common::String imageFilename = ci->directory + g_director->_dirSeparator + ci->fileName;
+			Common::Path location = findPath(imageFilename);
+			Common::SeekableReadStream *file = Common::MacResManager::openFileOrDataFork(location);
 			if (file) {
 				debugC(2, kDebugLoading, "****** Loading file '%s', cast id: %d", imageFilename.c_str(), imgId);
 				// Detect the filetype. Director will ignore file extensions, as do we.
@@ -561,10 +560,10 @@ void BitmapCastMember::load() {
 					return;
 				} else {
 					delete decoder;
-					warning("BUILDBOT: BitmapCastMember::load(): wrong format for external picture '%s'", path.toString().c_str());
+					warning("BUILDBOT: BitmapCastMember::load(): wrong format for external picture '%s'", location.toString().c_str());
 				}
 			} else {
-				warning("BitmapCastMember::load(): cannot open external picture '%s'", path.toString().c_str());
+				warning("BitmapCastMember::load(): cannot open external picture '%s'", location.toString().c_str());
 			}
 		}
 	} else {
diff --git a/engines/director/castmember/digitalvideo.cpp b/engines/director/castmember/digitalvideo.cpp
index 40f4028c872..3e8489c01ff 100644
--- a/engines/director/castmember/digitalvideo.cpp
+++ b/engines/director/castmember/digitalvideo.cpp
@@ -99,14 +99,18 @@ bool DigitalVideoCastMember::loadVideo(Common::String path) {
 	_filename = path;
 	_video = new Video::QuickTimeDecoder();
 
-	Common::String path1 = pathMakeRelative(path);
+	Common::Path location = findPath(path);
+	if (location.empty()) {
+		warning("DigitalVideoCastMember::loadVideo(): unable to resolve path %s", path.c_str());
+		return false;
+	}
 
-	debugC(2, kDebugLoading | kDebugImages, "Loading video %s -> %s", path.c_str(), path1.c_str());
-	bool result = _video->loadFile(Common::Path(path1, g_director->_dirSeparator));
+	debugC(2, kDebugLoading | kDebugImages, "Loading video %s -> %s", path.c_str(), location.toString().c_str());
+	bool result = _video->loadFile(location);
 	if (!result) {
 		delete _video;
 		_video = new Video::AVIDecoder();
-		result = _video->loadFile(Common::Path(path1, g_director->_dirSeparator));
+		result = _video->loadFile(location);
 		if (!result) {
 		    warning("DigitalVideoCastMember::loadVideo(): format not supported, skipping");
 		    delete _video;
@@ -242,7 +246,7 @@ uint DigitalVideoCastMember::getDuration() {
 	if (!_video || !_video->isVideoLoaded()) {
 		Common::String path = getCast()->getVideoPath(_castId);
 		if (!path.empty())
-			loadVideo(pathMakeRelative(path));
+			loadVideo(path);
 
 		_duration = getMovieTotalTime();
 	}
diff --git a/engines/director/castmember/sound.cpp b/engines/director/castmember/sound.cpp
index f851a31de04..4cdcf50b3d9 100644
--- a/engines/director/castmember/sound.cpp
+++ b/engines/director/castmember/sound.cpp
@@ -77,7 +77,7 @@ void SoundCastMember::load() {
 		// audio file is linked, load from the filesystem
 		CastMemberInfo *ci = _cast->getCastMemberInfo(_castId);
 		if (ci) {
-			Common::String filename = ci->fileName;
+			Common::String filename = ci->directory + g_director->_dirSeparator + ci->fileName;
 
 			debugC(2, kDebugLoading, "****** Loading file '%s', cast id: %d", filename.c_str(), sndId);
 			AudioFileDecoder *audio = new AudioFileDecoder(filename);
diff --git a/engines/director/channel.cpp b/engines/director/channel.cpp
index 21381e3d370..eeb5e3943f0 100644
--- a/engines/director/channel.cpp
+++ b/engines/director/channel.cpp
@@ -393,7 +393,7 @@ void Channel::setClean(Sprite *nextSprite, int spriteId, bool partial) {
 				Common::String path = nextSprite->_cast->getCast()->getVideoPath(nextSprite->_castId.member);
 
 				if (!path.empty()) {
-					((DigitalVideoCastMember *)nextSprite->_cast)->loadVideo(pathMakeRelative(path, true, false));
+					((DigitalVideoCastMember *)nextSprite->_cast)->loadVideo(path);
 					_movieTime = 0;
 					((DigitalVideoCastMember *)nextSprite->_cast)->startVideo(this);
 				}
diff --git a/engines/director/director.h b/engines/director/director.h
index 0d707fd8468..75457a90620 100644
--- a/engines/director/director.h
+++ b/engines/director/director.h
@@ -211,15 +211,15 @@ public:
 	Common::CodePage getPlatformEncoding();
 
 	Archive *createArchive();
-	Archive *openArchive(const Common::String movie);
-	void addArchiveToOpenList(const Common::String path);
-	Archive *loadEXE(const Common::String movie);
+	Archive *openArchive(const Common::Path &movie);
+	void addArchiveToOpenList(const Common::Path &path);
+	Archive *loadEXE(const Common::Path &movie);
 	Archive *loadEXEv3(Common::SeekableReadStream *stream);
 	Archive *loadEXEv4(Common::SeekableReadStream *stream);
 	Archive *loadEXEv5(Common::SeekableReadStream *stream);
 	Archive *loadEXEv7(Common::SeekableReadStream *stream);
 	Archive *loadEXERIFX(Common::SeekableReadStream *stream, uint32 offset);
-	Archive *loadMac(const Common::String movie);
+	Archive *loadMac(const Common::Path &movie);
 
 	bool desktopEnabled();
 
@@ -254,11 +254,11 @@ public:
 	Common::List<Common::String> _extraSearchPath;
 
 	// Owner of all Archive objects.
-	Common::HashMap<Common::String, Archive *, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _allSeenResFiles;
+	Common::HashMap<Common::Path, Archive *, Common::Path::IgnoreCaseAndMac_Hash, Common::Path::IgnoreCaseAndMac_EqualsTo> _allSeenResFiles;
 	// Handles to resource files that were opened by OpenResFile.
-	Common::HashMap<Common::String, Archive *, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _openResFiles;
+	Common::HashMap<Common::Path, Archive *, Common::Path::IgnoreCaseAndMac_Hash, Common::Path::IgnoreCaseAndMac_EqualsTo> _openResFiles;
 	// List of all currently open resource files
-	Common::List<Common::String> _allOpenResFiles;
+	Common::List<Common::Path> _allOpenResFiles;
 
 	Common::Array<Graphics::WinCursorGroup *> _winCursor;
 
diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp
index c6c9beffbc7..13b9e4b11f4 100644
--- a/engines/director/lingo/lingo-builtins.cpp
+++ b/engines/director/lingo/lingo-builtins.cpp
@@ -1178,14 +1178,23 @@ void LB::b_closeXlib(int nargs) {
 
 void LB::b_getNthFileNameInFolder(int nargs) {
 	int fileNum = g_lingo->pop().asInt() - 1;
-	Common::String path = pathMakeRelative(g_lingo->pop().asString(), true, false, true);
+	Common::String pathRaw = g_lingo->pop().asString();
+	if (pathRaw.empty()) {
+		// If we receive a blank string as a path, it shouldn't match anything.
+		g_lingo->push(Datum(""));
+		return;
+	}
+
+	Common::Path path = findPath(pathRaw, true, true, true);
 	// for directory, we either return the correct path, which we can access recursively.
 	// or we get a wrong path, which will lead us to a non-exist file node
 
-	Common::StringTokenizer directory_list(path, Common::String(g_director->_dirSeparator));
+	Common::StringArray directory_list = path.splitComponents();
 	Common::FSNode d = Common::FSNode(*g_director->getGameDataDir());
-	while (d.exists() && !directory_list.empty()) {
-		d = d.getChild(directory_list.nextToken());
+	for (auto &it : directory_list) {
+		d = d.getChild(it);
+		if (!d.exists())
+			break;
 	}
 
 	Datum r;
@@ -1196,7 +1205,7 @@ void LB::b_getNthFileNameInFolder(int nargs) {
 	if (cache) {
 		Common::ArchiveMemberList files;
 
-		cache->listMatchingMembers(files, path + (path.empty() ? "*" : "/*"), true);
+		cache->listMatchingMembers(files, path.toString() + (path.empty() ? "*" : "/*"), true);
 
 		for (auto &fi : files) {
 			fileNameList.push_back(Common::lastPathComponent(fi->getName(), '/'));
@@ -1207,7 +1216,7 @@ void LB::b_getNthFileNameInFolder(int nargs) {
 	if (d.exists()) {
 		Common::FSList f;
 		if (!d.getChildren(f, Common::FSNode::kListAll)) {
-			warning("Cannot access directory %s", path.c_str());
+			warning("Cannot access directory %s", path.toString().c_str());
 		} else {
 			for (uint i = 0; i < f.size(); i++)
 				fileNameList.push_back(f[i].getName());
@@ -1254,7 +1263,7 @@ void LB::b_openResFile(int nargs) {
 
 	if (!g_director->_allSeenResFiles.contains(resPath)) {
 		MacArchive *arch = new MacArchive();
-		if (arch->openFile(pathMakeRelative(resPath))) {
+		if (arch->openFile(findPath(resPath).toString())) {
 			g_director->_openResFiles.setVal(resPath, arch);
 			g_director->_allSeenResFiles.setVal(resPath, arch);
 			g_director->addArchiveToOpenList(resPath);
@@ -1323,7 +1332,7 @@ void LB::b_showResFile(int nargs) {
 		g_lingo->pop();
 	Common::String out;
 	for (auto &it : g_director->_allOpenResFiles)
-		out += it + "\n";
+		out += it.toString(g_director->_dirSeparator) + "\n";
 	g_debugger->debugLogFile(out, false);
 }
 
@@ -2048,7 +2057,7 @@ void LB::b_findEmpty(int nargs) {
 
 void LB::b_importFileInto(int nargs) {
 
-	Datum file = g_lingo->pop();
+	Common::String file = g_lingo->pop().asString();
 	Datum dst = g_lingo->pop();
 
 	if (!dst.isCastRef()) {
@@ -2058,17 +2067,17 @@ void LB::b_importFileInto(int nargs) {
 
 	CastMemberID memberID = *dst.u.cast;
 
-	if (!(file.asString().matchString("*.pic") || file.asString().matchString("*.pict"))) {
-		warning("LB::b_importFileInto : %s is not a valid PICT file", file.asString().c_str());
+	if (!(file.matchString("*.pic") || file.matchString("*.pict"))) {
+		warning("LB::b_importFileInto : %s is not a valid PICT file", file.c_str());
 		return;
 	}
 
-	Common::String path = pathMakeRelative(file.asString());
+	Common::Path path = findPath(file);
 	Common::File in;
 	in.open(path);
 
 	if (!in.isOpen()) {
-		warning("b_importFileInto(): Cannot open file %s", path.c_str());
+		warning("b_importFileInto(): Cannot open file %s", path.toString().c_str());
 		return;
 	}
 
@@ -3075,7 +3084,7 @@ void LB::b_sound(int nargs) {
 		TYPECHECK(firstArg, INT);
 		TYPECHECK(secondArg, STRING);
 
-		soundManager->playFile(pathMakeRelative(*secondArg.u.s), firstArg.u.i);
+		soundManager->playFile(*secondArg.u.s, firstArg.u.i);
 	} else {
 		warning("b_sound: unknown verb %s", verb.u.s->c_str());
 	}
diff --git a/engines/director/lingo/lingo-the.cpp b/engines/director/lingo/lingo-the.cpp
index f93939b2c9a..a102382159d 100644
--- a/engines/director/lingo/lingo-the.cpp
+++ b/engines/director/lingo/lingo-the.cpp
@@ -1467,7 +1467,7 @@ void Lingo::setTheSprite(Datum &id1, int field, Datum &d) {
 			if (castMember && castMember->_type == kCastDigitalVideo) {
 				Common::String path = castMember->getCast()->getVideoPath(castId.member);
 				if (!path.empty()) {
-					((DigitalVideoCastMember *)castMember)->loadVideo(pathMakeRelative(path));
+					((DigitalVideoCastMember *)castMember)->loadVideo(path);
 					((DigitalVideoCastMember *)castMember)->startVideo(channel);
 					// b_updateStage needs to have _videoPlayback set to render video
 					// in the regular case Score::renderSprites sets it.
diff --git a/engines/director/lingo/xlibs/aiff.cpp b/engines/director/lingo/xlibs/aiff.cpp
index 22603afd956..a70815f1f14 100644
--- a/engines/director/lingo/xlibs/aiff.cpp
+++ b/engines/director/lingo/xlibs/aiff.cpp
@@ -99,7 +99,7 @@ void AiffXObj::m_duration(int nargs) {
 	// Mac-ify any mac-paths to make them at least consistent:
 	Common::replace(filePath, "\\", ":");
 
-	auto aiffStream = Common::MacResManager::openFileOrDataFork(Common::Path(pathMakeRelative(filePath), g_director->_dirSeparator));
+	auto aiffStream = Common::MacResManager::openFileOrDataFork(findPath(filePath));
 	if (!aiffStream) {
 		error("Failed to open %s", filePath.c_str());
 	}
diff --git a/engines/director/lingo/xlibs/fileexists.cpp b/engines/director/lingo/xlibs/fileexists.cpp
index 07857b8b803..33a792a1a0b 100644
--- a/engines/director/lingo/xlibs/fileexists.cpp
+++ b/engines/director/lingo/xlibs/fileexists.cpp
@@ -71,8 +71,8 @@ void FileExists::m_fileexists(int nargs) {
 	Common::String filename = lastPathComponent(path, g_director->_dirSeparator);
 	if (!(saves->exists(filename))) {
 		Common::File *f = new Common::File;
-
-		if (!f->open(Common::Path(pathMakeRelative(origpath), g_director->_dirSeparator))) {
+		Common::Path location = findPath(origpath);
+		if (location.empty() || !f->open(location)) {
 			g_lingo->push(Datum(false));
 			return;
 		}
diff --git a/engines/director/lingo/xlibs/fileio.cpp b/engines/director/lingo/xlibs/fileio.cpp
index 928efc5e3ef..17b1ff019c9 100644
--- a/engines/director/lingo/xlibs/fileio.cpp
+++ b/engines/director/lingo/xlibs/fileio.cpp
@@ -244,8 +244,8 @@ void FileIO::m_new(int nargs) {
 		if (!me->_inStream) {
 			// Maybe we're trying to read one of the game files
 			Common::File *f = new Common::File;
-
-			if (!f->open(Common::Path(pathMakeRelative(origpath), g_director->_dirSeparator))) {
+			Common::Path location = findPath(origpath);
+			if (location.empty() || !f->open(location)) {
 				delete f;
 				saveFileError();
 				me->dispose();
diff --git a/engines/director/movie.cpp b/engines/director/movie.cpp
index 9130d5c263c..33a05f4288c 100644
--- a/engines/director/movie.cpp
+++ b/engines/director/movie.cpp
@@ -160,7 +160,8 @@ void Movie::loadCastLibMapping(Common::SeekableReadStreamEndian &stream) {
 		Archive *castArchive = _movieArchive;
 		bool isExternal = !path.empty();
 		if (isExternal) {
-			castArchive = loadExternalCastFrom(pathMakeRelative(path));
+			Common::Path archivePath = findMoviePath(path);
+			castArchive = loadExternalCastFrom(archivePath);
 			if (!castArchive) {
 				continue;	// couldn't load external cast
 			}
@@ -355,20 +356,20 @@ void Movie::clearSharedCast() {
 	_sharedCast = nullptr;
 }
 
-void Movie::loadSharedCastsFrom(Common::String filename) {
+void Movie::loadSharedCastsFrom(Common::Path &filename) {
 	clearSharedCast();
 
 	Archive *sharedCast = _vm->openArchive(filename);
 
 	if (!sharedCast) {
-		warning("loadSharedCastsFrom(): No shared cast %s", filename.c_str());
+		warning("loadSharedCastsFrom(): No shared cast %s", filename.toString().c_str());
 
 		return;
 	}
-	sharedCast->setPathName(filename);
+	sharedCast->setPathName(filename.toString(g_director->_dirSeparator));
 
 	debug(0, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
-	debug(0, "@@@@   Loading shared cast '%s'", filename.c_str());
+	debug(0, "@@@@   Loading shared cast '%s'", sharedCast->getFileName().c_str());
 	debug(0, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
 
 	_sharedCast = new Cast(this, DEFAULT_CAST_LIB, true, false);
@@ -376,17 +377,18 @@ void Movie::loadSharedCastsFrom(Common::String filename) {
 	_sharedCast->loadArchive();
 }
 
-Archive *Movie::loadExternalCastFrom(Common::String filename) {
-	Archive *externalCast = _vm->openArchive(filename);
+Archive *Movie::loadExternalCastFrom(Common::Path &filename) {
+	Archive *externalCast = nullptr;
+	externalCast = _vm->openArchive(filename);
 
 	if (!externalCast) {
-		warning("Movie::loadExternalCastFrom(): Cast file %s not found", filename.c_str());
+		warning("Movie::loadExternalCastFrom(): Cast file %s not found", filename.toString().c_str());
 
 		return nullptr;
 	}
 
 	debug(0, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
-	debug(0, "@@@@   Loading external cast '%s'", filename.c_str());
+	debug(0, "@@@@   Loading external cast '%s'", externalCast->getFileName().c_str());
 	debug(0, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
 
 	return externalCast;
diff --git a/engines/director/movie.h b/engines/director/movie.h
index e8ded21529e..657b0556254 100644
--- a/engines/director/movie.h
+++ b/engines/director/movie.h
@@ -104,8 +104,8 @@ public:
 	Score *getScore() const { return _score; }
 
 	void clearSharedCast();
-	void loadSharedCastsFrom(Common::String filename);
-	Archive *loadExternalCastFrom(Common::String filename);
+	void loadSharedCastsFrom(Common::Path &filename);
+	Archive *loadExternalCastFrom(Common::Path &filename);
 
 	CastMember *getCastMember(CastMemberID memberID);
 	CastMember *createOrReplaceCastMember(CastMemberID memberID, CastMember *cast);
diff --git a/engines/director/resource.cpp b/engines/director/resource.cpp
index a036b657d38..9ee2026c4a7 100644
--- a/engines/director/resource.cpp
+++ b/engines/director/resource.cpp
@@ -60,7 +60,8 @@ Common::Error Window::loadInitialMovie() {
 		return Common::kPathNotFile;
 
 	loadINIStream();
-	_mainArchive = g_director->openArchive(movie);
+	Common::Path path = findPath(movie);
+	_mainArchive = g_director->openArchive(path);
 
 	if (!_mainArchive) {
 		warning("Cannot open main movie");
@@ -79,8 +80,8 @@ Common::Error Window::loadInitialMovie() {
 
 	_currentMovie = new Movie(this);
 	_currentPath = getPath(movie, _currentPath);
-	Common::String sharedCastPath = getSharedCastPath();
-	if (!sharedCastPath.empty() && !sharedCastPath.equalsIgnoreCase(movie))
+	Common::Path sharedCastPath = getSharedCastPath();
+	if (!sharedCastPath.empty() && !(sharedCastPath == path))
 		_currentMovie->loadSharedCastsFrom(sharedCastPath);
 
 	// load startup movie
@@ -147,9 +148,9 @@ void Window::probeResources(Archive *archive) {
 				error("No strings in Projector file");
 
 			Common::String sname = decodePlatformEncoding(name->readPascalString());
-			Common::String moviePath = pathMakeRelative(sname);
-			if (testPath(moviePath)) {
-				_nextMovie.movie = moviePath;
+			Common::Path moviePath = findMoviePath(sname);
+			if (!moviePath.empty()) {
+				_nextMovie.movie = moviePath.toString(g_director->_dirSeparator);
 				warning("Replaced score name with: %s (from %s)", _nextMovie.movie.c_str(), sname.c_str());
 
 				if (_currentMovie) {
@@ -157,7 +158,7 @@ void Window::probeResources(Archive *archive) {
 					_currentMovie = nullptr;
 				}
 
-				Archive *subMovie = g_director->openArchive(moviePath);
+				Archive *subMovie = g_director->openArchive(moviePath.toString());
 				if (subMovie) {
 					probeResources(subMovie);
 				}
@@ -173,7 +174,8 @@ void Window::probeResources(Archive *archive) {
 		// fork of the file to state which XObject or HyperCard XCMD/XFCNs
 		// need to be loaded in.
 		MacArchive *resFork = new MacArchive();
-		if (resFork->openFile(archive->getPathName())) {
+		Common::String resForkPathName = archive->getPathName();
+		if (resFork->openFile(findPath(resForkPathName).toString())) {
 			if (resFork->hasResource(MKTAG('X', 'C', 'O', 'D'), -1)) {
 				Common::Array<uint16> xcod = resFork->getResourceIDList(MKTAG('X', 'C', 'O', 'D'));
 				for (auto &iterator : xcod) {
@@ -203,7 +205,7 @@ void Window::probeResources(Archive *archive) {
 	}
 }
 
-void DirectorEngine::addArchiveToOpenList(const Common::String path) {
+void DirectorEngine::addArchiveToOpenList(const Common::Path &path) {
 	// First, remove it if it is present
 	_allOpenResFiles.remove(path);
 
@@ -211,8 +213,8 @@ void DirectorEngine::addArchiveToOpenList(const Common::String path) {
 	_allOpenResFiles.push_front(path);
 }
 
-Archive *DirectorEngine::openArchive(const Common::String path) {
-	debug(1, "DirectorEngine::openArchive(\"%s\")", path.c_str());
+Archive *DirectorEngine::openArchive(const Common::Path &path) {
+	debug(1, "DirectorEngine::openArchive(\"%s\")", path.toString().c_str());
 
 	// If the archive is already open, don't reopen it;
 	// just init from the existing archive. This prevents errors that
@@ -235,7 +237,7 @@ Archive *DirectorEngine::openArchive(const Common::String path) {
 			return nullptr;
 		}
 	}
-	result->setPathName(path);
+	result->setPathName(path.toString(g_director->_dirSeparator));
 	_allSeenResFiles.setVal(path, result);
 
 	addArchiveToOpenList(path);
@@ -262,11 +264,10 @@ void Window::loadINIStream() {
 	}
 }
 
-Archive *DirectorEngine::loadEXE(const Common::String movie) {
-	Common::Path path(movie, g_director->_dirSeparator);
-	Common::SeekableReadStream *exeStream = SearchMan.createReadStreamForMember(path);
+Archive *DirectorEngine::loadEXE(const Common::Path &movie) {
+	Common::SeekableReadStream *exeStream = SearchMan.createReadStreamForMember(movie);
 	if (!exeStream) {
-		debugC(5, kDebugLoading, "DirectorEngine::loadEXE(): Failed to open file '%s'", movie.c_str());
+		debugC(5, kDebugLoading, "DirectorEngine::loadEXE(): Failed to open file '%s'", movie.toString().c_str());
 		return nullptr;
 	}
 
@@ -280,14 +281,14 @@ Archive *DirectorEngine::loadEXE(const Common::String movie) {
 		result = new RIFFArchive();
 
 		if (!result->openStream(exeStream, 0)) {
-			debugC(5, kDebugLoading, "Window::loadEXE(): Failed to load RIFF from '%s'", movie.c_str());
+			debugC(5, kDebugLoading, "Window::loadEXE(): Failed to load RIFF from '%s'", movie.toString().c_str());
 			delete result;
 			return nullptr;
 		}
 	} else {
-		Common::WinResources *exe = Common::WinResources::createFromEXE(path.toString());
+		Common::WinResources *exe = Common::WinResources::createFromEXE(movie.toString());
 		if (!exe) {
-			debugC(5, kDebugLoading, "DirectorEngine::loadEXE(): Failed to open EXE '%s'", path.toString().c_str());
+			debugC(5, kDebugLoading, "DirectorEngine::loadEXE(): Failed to open EXE '%s'", movie.toString().c_str());
 			delete exeStream;
 			return nullptr;
 		}
@@ -329,13 +330,13 @@ Archive *DirectorEngine::loadEXE(const Common::String movie) {
 		}
 
 		if (result)
-			result->setPathName(movie);
+			result->setPathName(movie.toString(g_director->_dirSeparator));
 
 		return result;
 	}
 
 	if (result)
-		result->setPathName(movie);
+		result->setPathName(movie.toString(g_director->_dirSeparator));
 	else
 		delete exeStream;
 
@@ -501,7 +502,7 @@ Archive *DirectorEngine::loadEXERIFX(Common::SeekableReadStream *stream, uint32
 	return result;
 }
 
-Archive *DirectorEngine::loadMac(const Common::String movie) {
+Archive *DirectorEngine::loadMac(const Common::Path &movie) {
 	Archive *result = nullptr;
 	if (g_director->getVersion() < 400) {
 		// The data is part of the resource fork of the executable
@@ -510,17 +511,17 @@ Archive *DirectorEngine::loadMac(const Common::String movie) {
 		if (!result->openFile(movie)) {
 			delete result;
 			result = nullptr;
-			debugC(5, kDebugLoading, "DirectorEngine::loadMac(): Could not open '%s'", movie.c_str());
+			debugC(5, kDebugLoading, "DirectorEngine::loadMac(): Could not open '%s'", movie.toString().c_str());
 		}
 	} else {
 		// The RIFX is located in the data fork of the executable
-		Common::SeekableReadStream *dataFork = Common::MacResManager::openFileOrDataFork(Common::Path(movie, g_director->_dirSeparator));
+		Common::SeekableReadStream *dataFork = Common::MacResManager::openFileOrDataFork(movie);
 		if (!dataFork) {
-			debugC(5, kDebugLoading, "DirectorEngine::loadMac(): Failed to open Mac binary '%s'", movie.c_str());
+			debugC(5, kDebugLoading, "DirectorEngine::loadMac(): Failed to open Mac binary '%s'", movie.toString().c_str());
 			return nullptr;
 		}
 		result = new RIFXArchive();
-		result->setPathName(movie);
+		result->setPathName(movie.toString(g_director->_dirSeparator));
 
 		// First we need to detect PPC vs. 68k
 
diff --git a/engines/director/sound.cpp b/engines/director/sound.cpp
index 5dda6ded140..eca3cb930f9 100644
--- a/engines/director/sound.cpp
+++ b/engines/director/sound.cpp
@@ -852,11 +852,9 @@ Audio::AudioStream *AudioFileDecoder::getAudioStream(bool looping, bool forPuppe
 	if (_path.empty())
 		return nullptr;
 
-	Common::Path filePath = Common::Path(pathMakeRelative(_path), g_director->_dirSeparator);
-
-	Common::SeekableReadStream *copiedStream = Common::MacResManager::openFileOrDataFork(filePath);
-
-	if (copiedStream == nullptr) {
+	Common::Path newPath = findPath(_path);
+	Common::SeekableReadStream *copiedStream = Common::MacResManager::openFileOrDataFork(newPath);
+	if (!copiedStream) {
 		warning("Failed to open %s", _path.c_str());
 		return nullptr;
 	}
diff --git a/engines/director/tests.cpp b/engines/director/tests.cpp
index 99ac552364a..98b795eb48f 100644
--- a/engines/director/tests.cpp
+++ b/engines/director/tests.cpp
@@ -36,6 +36,7 @@
 #include "director/archive.h"
 #include "director/movie.h"
 #include "director/picture.h"
+#include "director/util.h"
 #include "director/window.h"
 #include "director/lingo/lingo.h"
 
@@ -110,7 +111,8 @@ void Window::testFontScaling() {
 		x += tile->_surface.w + 10;
 	}
 
-	Common::String path = pathMakeRelative("blend2.pic");
+	Common::String filename("blend2.pic");
+	Common::Path path = findPath(filename);
 	Common::File in;
 	in.open(path);
 
@@ -123,7 +125,7 @@ void Window::testFontScaling() {
 		surface.blitFrom(res, Common::Point(400, 280));
 		in.close();
 	} else {
-		warning("b_importFileInto(): Cannot open file %s", path.c_str());
+		warning("b_importFileInto(): Cannot open file %s", path.toString().c_str());
 	}
 
 	g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, w, h); // testing fonts
diff --git a/engines/director/util.cpp b/engines/director/util.cpp
index f77a98160fb..46b68526651 100644
--- a/engines/director/util.cpp
+++ b/engines/director/util.cpp
@@ -25,6 +25,7 @@
 #include "common/macresman.h"
 #include "common/memstream.h"
 #include "common/punycode.h"
+#include "common/str-array.h"
 #include "common/tokenizer.h"
 #include "common/compression/zlib.h"
 
@@ -516,36 +517,57 @@ Common::String getPath(Common::String path, Common::String cwd) {
 	return cwd; // The path is not altered
 }
 
-bool testPath(Common::String &path, bool directory) {
-	Common::FSNode d = Common::FSNode(*g_director->getGameDataDir());
-	Common::FSNode node;
+Common::String convert83Path(Common::String &path) {
+	Common::String addedexts;
+	Common::String convPath;
 
-	// Test if we have it right in the SearchMan. Also accept MacBinary
-	// for Mac and Pippin
-	if (SearchMan.hasFile(Common::Path(path, g_director->_dirSeparator)) ||
-	    ((g_director->getPlatform() == Common::kPlatformMacintoshII
-	      || g_director->getPlatform() == Common::kPlatformMacintosh
-	      || g_director->getPlatform() == Common::kPlatformPippin) &&
-	     Common::MacResManager::exists(Common::Path(path, g_director->_dirSeparator))))
-		return true;
+	const char *ptr = path.c_str();
+	Common::String component;
 
-	debugN(9, "%s", recIndent());
-	debug(9, "testPath: %s  dir: %d", path.c_str(), directory);
+	while (*ptr) {
+		if (*ptr == g_director->_dirSeparator) {
+			if (component.equals(".")) {
+				convPath += component;
+			} else {
+				convPath += convertMacFilename(component.c_str());
+			}
 
-	// check for the game data dir
-	if (!path.contains(g_director->_dirSeparator) && path.equalsIgnoreCase(d.getName())) {
-		if (!directory)
-			return false;
-		path = "";
-		return true;
+			component.clear();
+			convPath += g_director->_dirSeparator;
+		} else {
+			component += *ptr;
+		}
+
+		ptr++;
 	}
 
+	if (hasExtension(component)) {
+		Common::String nameWithoutExt = component.substr(0, component.size() - 4);
+		Common::String ext = component.substr(component.size() - 4);
+		convPath += convertMacFilename(nameWithoutExt.c_str()) + ext;
+	} else {
+		convPath += convertMacFilename(component.c_str());
+	}
+
+	return convPath;
+}
+
+Common::FSNode resolvePath(Common::String &path, Common::FSNode &base, bool directory) {
+	Common::FSNode d;
+	if (base.exists() && base.isDirectory()) {
+		d = base;
+	} else {
+		d = Common::FSNode(*g_director->getGameDataDir());
+	}
+	Common::FSNode node;
+	path = convertPath(path);
+
 	Common::StringTokenizer directory_list(path, Common::String(g_director->_dirSeparator));
 	Common::String newPath;
 
 	Common::FSList fslist;
 	while (!directory_list.empty()) {
-		Common::String token = directory_list.nextToken();
+		Common::String token = punycode_decodefilename(directory_list.nextToken());
 		fslist.clear();
 		Common::FSNode::ListMode mode = Common::FSNode::kListDirectoriesOnly;
 		if (directory_list.empty() && !directory) {
@@ -560,9 +582,9 @@ bool testPath(Common::String &path, bool directory) {
 			// for each element in the path, choose the first FSNode
 			// with a case-insensitive matching name
 			if (i.getName().equalsIgnoreCase(token)) {
-				// If this the final path component, check if we're allowed to match with a directory
 				node = Common::FSNode(i);
-				if (directory_list.empty() && !directory && node.isDirectory()) {
+				// If this the final path component, check if we're allowed to match with a directory
+				if (directory_list.empty() && (directory != node.isDirectory())) {
 					continue;
 				}
 
@@ -577,256 +599,176 @@ bool testPath(Common::String &path, bool directory) {
 		}
 		if (!exists) {
 			debugN(9, "%s", recIndent());
-			debug(9, "testPath: Not exists");
-			return false;
+			debug(9, "resolvePath(): No match found for %s", path.c_str());
+			return Common::FSNode();
 		}
 	}
 	debugN(9, "%s", recIndent());
-	debug(9, "testPath: ***** HAVE MATCH");
-	// write back path with correct case
-	path = newPath;
-	return true;
+	debug(9, "resolvePath(): Found match for %s -> %s", path.c_str(), d.getPath().c_str());
+	return d;
 }
 
-
-Common::String pathMakeRelative(Common::String path, bool recursive, bool addexts, bool directory) {
-	//Wrap pathMakeRelative to search in extra paths defined by the game
-	Common::String foundPath;
-
-	recLevel = 0;
-
-	debugN(8, "%s", recIndent());
-	debug(8, "pathMakeRelative(\"%s\", recursive: %d, addexts: %d, directory: %d):", path.c_str(), recursive, addexts, directory);
-	bool isAbsolute = isAbsolutePath(path);
+Common::FSNode resolvePartialPath(Common::String &path, Common::FSNode &base, bool directory) {
 	path = convertPath(path);
-	debugN(9, "%s", recIndent());
-	debug(9, "pathMakeRelative(): converted -> %s", path.c_str());
-
-	// Absolute paths should not be matched against the global search path list
-	if (isAbsolute) {
-		return wrappedPathMakeRelative(path, recursive, addexts, directory, true);
-	}
-
-	Datum searchPath = g_director->getLingo()->_searchPath;
-	if (searchPath.type == ARRAY && searchPath.u.farr->arr.size() > 0) {
-		for (uint i = 0; i < searchPath.u.farr->arr.size(); i++) {
-			Common::String searchIn = searchPath.u.farr->arr[i].asString();
-			// Ensure there's a trailing directory separator
-			Common::String separator = Common::String::format("%c", g_director->_dirSeparator);
-			if (!searchIn.hasSuffix(separator)) {
-				searchIn += separator;
+	Common::StringArray tokens = Common::StringTokenizer(path, Common::String(g_director->_dirSeparator)).split();
+
+	Common::FSNode result;
+	while (tokens.size()) {
+		Common::String subpath;
+		for (uint i = 0; i < tokens.size(); i++) {
+			subpath += tokens[i];
+			if (i < tokens.size() - 1) {
+				subpath += g_director->_dirSeparator;
 			}
-			debugN(9, "%s", recIndent());
-			debug(9, "pathMakeRelative(): searchPath: %s", searchIn.c_str());
-
-			recLevel++;
-			foundPath = wrappedPathMakeRelative(searchIn + path, recursive, addexts, directory, false);
-			recLevel--;
-
-			if (testPath(foundPath, directory))
-				return foundPath;
-
-			debugN(9, "%s", recIndent());
-			debug(9, "pathMakeRelative(): -- searchPath not found: %s", foundPath.c_str());
 		}
+		result = resolvePath(subpath, base, directory);
+		if (result.exists()) {
+			break;
+		}
+		tokens.remove_at(0);
 	}
+	return result;
+}
 
-	for (auto i = g_director->_extraSearchPath.begin(); i != g_director->_extraSearchPath.end(); ++i) {
-		debugN(9, "%s", recIndent());
-		debug(9, "pathMakeRelative(): extraSearchPath: %s", i->c_str());
-
-		recLevel++;
-		foundPath = wrappedPathMakeRelative(*i + path, recursive, addexts, directory, false);
-		recLevel--;
-
-		if (testPath(foundPath, directory))
-			return foundPath;
-
-		debugN(9, "%s", recIndent());
-		debug(9, "pathMakeRelative(): -- extraSearchPath not found: %s", foundPath.c_str());
+Common::FSNode resolvePathWithFuzz(Common::String &path, Common::FSNode &base, bool directory) {
+	Common::FSNode result = resolvePath(path, base, directory);
+	if (!result.exists()) {
+		// Try again with all non-FAT compatible characters stripped
+		Common::String newPath = stripMacPath(path.c_str());
+		if (newPath != path)
+			result = resolvePath(newPath, base, directory);
+	}
+	if (!result.exists()) {
+		// Try again with the path horribly disfigured to fit into 8.3 DOS filenames
+		Common::String newPath = convert83Path(path);
+		if (newPath != path)
+			result = resolvePath(newPath, base, directory);
 	}
-	return wrappedPathMakeRelative(path, recursive, addexts, directory, false);
+	return result;
 }
 
-
-// if we are finding the file path, then this func will return exactly the executable file path
-// if we are finding the directory path, then we will get the path relative to the game data dir.
-// e.g. if we have game data dir as SSwarlock, then "A:SSwarlock" -> "", "A:SSwarlock:Nav" -> "Nav"
-Common::String wrappedPathMakeRelative(Common::String path, bool recursive, bool addexts, bool directory, bool absolute) {
-
-	Common::String initialPath(path);
-
-	debugN(9, "%s", recIndent());
-	debug(9, "wrappedPathMakeRelative(): s0 %s -> %s", path.c_str(), initialPath.c_str());
-
-	if (recursive) // first level
-		initialPath = convertPath(initialPath);
-
-	debugN(9, "%s", recIndent());
-	debug(9, "wrappedPathMakeRelative(): s1 %s -> %s", path.c_str(), initialPath.c_str());
-
-	if (absolute) {
-		initialPath = Common::normalizePath(initialPath, g_director->_dirSeparator);
-	} else {
-		initialPath = Common::normalizePath(g_director->getCurrentPath() + initialPath, g_director->_dirSeparator);
+Common::FSNode resolvePartialPathWithFuzz(Common::String &path, Common::FSNode &base, bool directory) {
+	Common::FSNode result = resolvePartialPath(path, base, directory);
+	if (!result.exists()) {
+		// Try again with all non-FAT compatible characters stripped
+		Common::String newPath = stripMacPath(path.c_str());
+		result = resolvePartialPath(newPath, base, directory);
 	}
-	Common::String convPath = initialPath;
+	if (!result.exists()) {
+		// Try again with the path horribly disfigured to fit into 8.3 DOS filenames
+		Common::String newPath = convert83Path(path);
+		result = resolvePartialPath(newPath, base, directory);
+	}
+	return result;
+}
 
-	debugN(9, "%s", recIndent());
-	debug(9, "wrappedPathMakeRelative(): s2 %s", convPath.c_str());
+Common::Path nodeToPath(Common::FSNode &node) {
+	Common::StringArray base = Common::Path(g_director->getGameDataDir()->getPath()).splitComponents();
+	Common::StringArray target = Common::Path(node.getPath()).splitComponents();
 
-	if (testPath(initialPath, directory))
-		return initialPath;
+	if (target.size() < base.size()) {
+		warning("nodeToPath(): target not a subset");
+		return Common::Path();
+	}
+	for (auto &it : base) {
+		if (it != target[0]) {
+			warning("nodeToPath(): expected component %s, found %s", it.c_str(), target[0].c_str());
+			return Common::Path();
+		}
+		target.remove_at(0);
+	}
+	Common::Path result = Common::Path::joinComponents(target);
+	debug(9, "nodeToPath(): %s -> %s", node.getPath().c_str(), result.toString().c_str());
+	return result;
+}
 
+Common::Path findPath(Common::String &path, bool currentFolder, bool searchPaths, bool directory) {
+	Common::FSNode result, base;
 	debugN(9, "%s", recIndent());
-	debug(9, "wrappedPathMakeRelative(): s2.1 -- not found %s", initialPath.c_str());
-
-	// Now try to search the file
-	bool opened = false;
-
-	while (convPath.contains(g_director->_dirSeparator)) {
-		int pos = convPath.find(g_director->_dirSeparator);
-		convPath = Common::String(&convPath.c_str()[pos + 1]);
-
+	debug(9, "findPath(): beginning search for \"%s\"", path.c_str());
+	// For an absolute path, first check it relative to the filesystem
+	if (isAbsolutePath(path)) {
 		debugN(9, "%s", recIndent());
-		debug(9, "wrappedPathMakeRelative(): s3 try %s", convPath.c_str());
-
-		if (!testPath(convPath, directory)) {
-			// If we were supplied a path with subdirectories,
-			// attempt to combine it with the current movie path at every iteration
-			Common::String locPath = Common::normalizePath(g_director->getCurrentPath() + convPath, g_director->_dirSeparator);
+		debug(9, "findPath(): searching absolute path");
+		result = resolvePathWithFuzz(path, base, directory);
+		if (result.exists()) {
 			debugN(9, "%s", recIndent());
-			debug(9, "wrappedPathMakeRelative(): s3.1 try %s", locPath.c_str());
-
-			if (!testPath(locPath, directory)) {
-				debugN(9, "%s", recIndent());
-				debug(9, "wrappedPathMakeRelative(): s3.1 -- not found %s", locPath.c_str());
-				continue;
-			}
+			debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), nodeToPath(result).toString().c_str());
+			return nodeToPath(result);
 		}
-
-		debug(9, "wrappedPathMakeRelative(): s3 converted %s -> %s", path.c_str(), convPath.c_str());
-
-		opened = true;
-
-		break;
 	}
 
-	if (!opened) {
-		// Try stripping all of the characters not allowed in FAT
-		convPath = stripMacPath(initialPath.c_str());
-
-		debugN(9, "%s", recIndent());
-		debug(9, "wrappedPathMakeRelative(): s4 %s", convPath.c_str());
-
-		if (testPath(initialPath, directory))
-			return initialPath;
-
+	if (currentFolder) {
+		Common::String currentPath = g_director->getCurrentPath();
+		Common::FSNode current = resolvePath(currentPath, base, true);
 		debugN(9, "%s", recIndent());
-		debug(9, "wrappedPathMakeRelative(): s4.1 -- not found %s", initialPath.c_str());
-
-		// Now try to search the file
-		while (convPath.contains(g_director->_dirSeparator)) {
-			int pos = convPath.find(g_director->_dirSeparator);
-			convPath = Common::String(&convPath.c_str()[pos + 1]);
-
-			debugN(9, "%s", recIndent());
-			debug(9, "wrappedPathMakeRelative(): s5 try %s", convPath.c_str());
-
-			if (!testPath(convPath, directory)) {
-				debugN(9, "%s", recIndent());
-				debug(9, "wrappedPathMakeRelative(): s5 -- not found %s", convPath.c_str());
-				continue;
-			}
-
+		debug(9, "findPath(): searching current folder %s", current.getPath().c_str());
+		base = current;
+		result = resolvePartialPathWithFuzz(path, base, directory);
+		if (result.exists()) {
 			debugN(9, "%s", recIndent());
-			debug(9, "wrappedPathMakeRelative(): s5 converted %s -> %s", path.c_str(), convPath.c_str());
-
-			opened = true;
-
-			break;
+			debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), nodeToPath(result).toString().c_str());
+			return nodeToPath(result);
 		}
 	}
 
-	if (!opened && recursive && !directory) {
-		// Hmmm. We couldn't find the path as is.
-		// Let's try to translate file path into 8.3 format
-		Common::String addedexts;
-
-		convPath.clear();
-		const char *ptr = initialPath.c_str();
-		Common::String component;
-
-		while (*ptr) {
-			if (*ptr == g_director->_dirSeparator) {
-				if (component.equals(".")) {
-					convPath += component;
-				} else {
-					convPath += convertMacFilename(component.c_str());
-				}
+	// Fall back to checking the game root path
+	debugN(9, "%s", recIndent());
+	debug(9, "findPath(): searching game root path");
+	base = Common::FSNode();
+	result = resolvePartialPathWithFuzz(path, base, directory);
+	if (result.exists()) {
+		debugN(9, "%s", recIndent());
+		debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), nodeToPath(result).toString().c_str());
+		return nodeToPath(result);
+	}
 
-				component.clear();
-				convPath += g_director->_dirSeparator;
-			} else {
-				component += *ptr;
+	// Check each of the search paths in sequence
+	if (searchPaths) {
+		Common::Array<Common::String> searchPathList;
+		Datum searchPath = g_director->getLingo()->_searchPath;
+		if (searchPath.type == ARRAY) {
+			for (auto &it : searchPath.u.farr->arr) {
+				searchPathList.push_back(it.asString());
 			}
-
-			ptr++;
 		}
-
-		if (g_director->getPlatform() == Common::kPlatformWindows) {
-			if (hasExtension(component)) {
-				Common::String nameWithoutExt = component.substr(0, component.size() - 4);
-				Common::String ext = component.substr(component.size() - 4);
-				Common::String newpath = convPath + convertMacFilename(nameWithoutExt.c_str()) + ext;
-
-				debugN(9, "%s", recIndent());
-				debug(9, "wrappedPathMakeRelative(): s6 %s -> try %s", initialPath.c_str(), newpath.c_str());
-
-				recLevel++;
-				Common::String res = wrappedPathMakeRelative(newpath, false, false);
-				recLevel--;
-
-				if (testPath(res))
-					return res;
-
+		for (auto &it : g_director->_extraSearchPath) {
+			searchPathList.push_back(it);
+		}
+		for (auto &searchIn : searchPathList) {
+			// Ensure there's a trailing directory separator
+			Common::String separator = Common::String::format("%c", g_director->_dirSeparator);
+			if (!searchIn.hasSuffix(separator)) {
+				searchIn += separator;
+			}
+			base = Common::FSNode();
+			base = resolvePathWithFuzz(searchIn, base, true);
+			if (!base.exists()) {
 				debugN(9, "%s", recIndent());
-				debug(9, "wrappedPathMakeRelative(): s6 -- not found %s", res.c_str());
-
+				debug(9, "findPath(): couldn't resolve search path folder %s, skipping", searchIn.c_str());
+				continue;
+			}
+			debugN(9, "%s", recIndent());
+			debug(9, "findPath(): searching search path folder %s", searchIn.c_str());
+			result = resolvePartialPathWithFuzz(path, base, directory);
+			if (result.exists()) {
 				debugN(9, "%s", recIndent());
-				debug(9, "wrappedPathMakeRelative(): s7 -- try alternate extensions for %s in %s", nameWithoutExt.c_str(), initialPath.c_str());
-				addedexts = testExtensions(nameWithoutExt, initialPath, convPath);
-				if (!addedexts.empty())
-					return addedexts;
-
+				debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), nodeToPath(result).toString().c_str());
+				return nodeToPath(result);
 			}
 		}
-
-		if (addexts)
-			addedexts = testExtensions(component, initialPath, convPath);
-
-		if (!addedexts.empty()) {
-			return addedexts;
-		}
-
-		return initialPath;	// Anyway nothing good is happening
 	}
 
-	if (opened)
-		return convPath;
-	else
-		return initialPath;
+	// Return empty path
+	debug(9, "findPath(): failed to resolve \"%s\"", path.c_str());
+	return Common::Path();
 }
 
-bool hasExtension(Common::String filename) {
-	uint len = filename.size();
-	return len >= 4 && filename[len - 4] == '.'
-					&& Common::isAlpha(filename[len - 3])
-					&& Common::isAlpha(filename[len - 2])
-					&& Common::isAlpha(filename[len - 1]);
-}
+Common::Path findMoviePath(Common::String &path, bool currentFolder, bool searchPaths) {
+	Common::Path result = findPath(path, currentFolder, searchPaths, false);
+	if (!result.empty())
+		return result;
 
-Common::String testExtensions(Common::String component, Common::String initialPath, Common::String convPath) {
 	const char *extsD3[] = { ".MMM", nullptr };
 	const char *extsD4[] = { ".DIR", ".DXR", ".EXE", nullptr };
 	const char *extsD5[] = { ".DIR", ".DXR", ".CST", ".CXT", ".EXE", nullptr };
@@ -839,31 +781,31 @@ Common::String testExtensions(Common::String component, Common::String initialPa
 	} else if (g_director->getVersion() >= 500 && g_director->getVersion() < 600) {
 		exts = extsD5;
 	} else {
-		warning("STUB: testExtensions(): file extensions not yet supported for version %d, falling back to D5", g_director->getVersion());
+		warning("findMoviePath(): file extensions not yet supported for version %d, falling back to D5", g_director->getVersion());
 		exts = extsD5;
 	}
-	for (int i = 0; exts[i]; ++i) {
-		Common::String newpath = convPath + component.c_str() + exts[i];
 
-		debugN(9, "%s", recIndent());
-		debug(9, "testExtensions(): sT %s -> try %s, comp: %s", initialPath.c_str(), newpath.c_str(), component.c_str());
-		Common::String res = wrappedPathMakeRelative(newpath, false, false);
+	Common::String fileBase = path;
+	if (hasExtension(fileBase))
+		fileBase = fileBase.substr(0, fileBase.size() - 4);
 
-		if (testPath(res))
-			return res;
-	}
-	for (int i = 0; exts[i]; ++i) {
-		Common::String newpath = convPath + convertMacFilename(component.c_str()) + exts[i];
-
-		debugN(9, "%s", recIndent());
-		debug(9, "testExtensions(): sT %s -> try %s, comp: %s", initialPath.c_str(), newpath.c_str(), component.c_str());
-		Common::String res = wrappedPathMakeRelative(newpath, false, false);
+	for (int i = 0; exts[i]; i++) {
+		Common::String newPath = fileBase + exts[i];
 
-		if (testPath(res))
-			return res;
+		result = findPath(newPath, currentFolder, searchPaths, false);
+		if (!result.empty())
+			break;
 	}
+	return result;
+}
+
 
-	return Common::String();
+bool hasExtension(Common::String filename) {
+	uint len = filename.size();
+	return len >= 4 && filename[len - 4] == '.'
+					&& Common::isAlpha(filename[len - 3])
+					&& Common::isAlpha(filename[len - 2])
+					&& Common::isAlpha(filename[len - 1]);
 }
 
 Common::String getFileName(Common::String path) {
diff --git a/engines/director/util.h b/engines/director/util.h
index ce23ff548e5..df31c72ea67 100644
--- a/engines/director/util.h
+++ b/engines/director/util.h
@@ -24,6 +24,8 @@
 
 namespace Common {
 class String;
+class FSNode;
+class Path;
 }
 
 namespace Director {
@@ -39,16 +41,17 @@ Common::String unixToMacPath(const Common::String &path);
 
 Common::String getPath(Common::String path, Common::String cwd);
 
-bool testPath(Common::String &path, bool directory = false);
+Common::FSNode resolvePath(Common::String &path, Common::FSNode &base, bool directory);
+Common::FSNode resolvePartialPath(Common::String &path, Common::FSNode &base, bool directory);
+Common::FSNode resolvePathWithFuzz(Common::String &path, Common::FSNode &base, bool directory);
+Common::FSNode resolvePartialPathWithFuzz(Common::String &path, Common::FSNode &base, bool directory);
+Common::Path nodeToPath(Common::FSNode &node);
+Common::Path findPath(Common::String &path, bool currentFolder = true, bool searchPaths = true, bool directory = false);
+Common::Path findMoviePath(Common::String &path, bool currentFolder = true, bool searchPaths = true);
 
-Common::String pathMakeRelative(Common::String path, bool recursive = true, bool addexts = true, bool directory = false);
-
-Common::String wrappedPathMakeRelative(Common::String path, bool recursive = true, bool addexts = true, bool directory = false, bool absolute = false);
 
 bool hasExtension(Common::String filename);
 
-Common::String testExtensions(Common::String component, Common::String initialPath, Common::String convPath);
-
 Common::String getFileName(Common::String path);
 
 Common::String stripMacPath(const char *name);
diff --git a/engines/director/window.cpp b/engines/director/window.cpp
index a266a377487..ee18011769d 100644
--- a/engines/director/window.cpp
+++ b/engines/director/window.cpp
@@ -313,23 +313,23 @@ void Window::setVisible(bool visible, bool silent) {
 }
 
 bool Window::setNextMovie(Common::String &movieFilenameRaw) {
-	Common::String movieFilename = pathMakeRelative(movieFilenameRaw);
+	Common::Path movieFilename = findMoviePath(movieFilenameRaw);
 
 	bool fileExists = false;
 	Common::File file;
-	if (file.open(Common::Path(movieFilename, _vm->_dirSeparator))) {
+	if (!movieFilename.empty() && file.open(movieFilename)) {
 		fileExists = true;
 		file.close();
 	}
 
-	debug(1, "Window::setNextMovie: '%s' -> '%s' -> '%s'", movieFilenameRaw.c_str(), convertPath(movieFilenameRaw).c_str(), movieFilename.c_str());
+	debug(1, "Window::setNextMovie: '%s' -> '%s' -> '%s'", movieFilenameRaw.c_str(), convertPath(movieFilenameRaw).c_str(), movieFilename.toString().c_str());
 
 	if (!fileExists) {
-		warning("Movie %s does not exist", movieFilename.c_str());
+		warning("Movie %s does not exist", movieFilename.toString().c_str());
 		return false;
 	}
 
-	_nextMovie.movie = movieFilename;
+	_nextMovie.movie = movieFilename.toString(g_director->_dirSeparator);
 	return true;
 }
 
@@ -344,19 +344,19 @@ void Window::updateBorderType() {
 }
 
 void Window::loadNewSharedCast(Cast *previousSharedCast) {
-	Common::String previousSharedCastPath;
-	Common::String newSharedCastPath = getSharedCastPath();
+	Common::Path previousSharedCastPath;
+	Common::Path newSharedCastPath = getSharedCastPath();
 	if (previousSharedCast && previousSharedCast->getArchive()) {
-		previousSharedCastPath = previousSharedCast->getArchive()->getPathName();
+		previousSharedCastPath = Common::Path(previousSharedCast->getArchive()->getPathName(), g_director->_dirSeparator);
 	}
 
 	// Check if previous and new sharedCasts are the same
-	if (!previousSharedCastPath.empty() && previousSharedCastPath.equalsIgnoreCase(newSharedCastPath)) {
+	if (!previousSharedCastPath.empty() && previousSharedCastPath == newSharedCastPath) {
 		// Clear those previous widget pointers
 		previousSharedCast->releaseCastMemberWidget();
 		_currentMovie->_sharedCast = previousSharedCast;
 
-		debugC(1, kDebugLoading, "Skipping loading already loaded shared cast, path: %s", previousSharedCastPath.c_str());
+		debugC(1, kDebugLoading, "Skipping loading already loaded shared cast, path: %s", previousSharedCastPath.toString().c_str());
 		return;
 	}
 
@@ -386,7 +386,9 @@ bool Window::loadNextMovie() {
 	delete _currentMovie;
 	_currentMovie = nullptr;
 
-	Archive *mov = g_director->openArchive(_currentPath + Common::lastPathComponent(_nextMovie.movie, g_director->_dirSeparator));
+	Common::Path archivePath = Common::Path(_currentPath, g_director->_dirSeparator);
+	archivePath.appendInPlace(Common::lastPathComponent(_nextMovie.movie, g_director->_dirSeparator));
+	Archive *mov = g_director->openArchive(archivePath);
 
 	if (!mov)
 		return false;
@@ -479,7 +481,7 @@ bool Window::step() {
 	return false;
 }
 
-Common::String Window::getSharedCastPath() {
+Common::Path Window::getSharedCastPath() {
 	Common::Array<Common::String> namesToTry;
 	if (_vm->getVersion() < 400) {
 		if (g_director->getPlatform() == Common::kPlatformWindows) {
@@ -496,15 +498,14 @@ Common::String Window::getSharedCastPath() {
 		namesToTry.push_back("Shared.cxt");
 	}
 
+	Common::Path result;
 	for (uint i = 0; i < namesToTry.size(); i++) {
-		Common::File f;
-		if (f.open(Common::Path(_currentPath + namesToTry[i], _vm->_dirSeparator))) {
-			f.close();
-			return _currentPath + namesToTry[i];
-		}
+		result = findMoviePath(namesToTry[i]);
+		if (!result.empty())
+			return result;
 	}
 
-	return Common::String();
+	return result;
 }
 
 void Window::freezeLingoState() {
diff --git a/engines/director/window.h b/engines/director/window.h
index 95c0232ac19..b704fe99f21 100644
--- a/engines/director/window.h
+++ b/engines/director/window.h
@@ -151,7 +151,7 @@ public:
 	bool loadNextMovie();
 	void loadNewSharedCast(Cast *previousSharedCast);
 
-	Common::String getSharedCastPath();
+	Common::Path getSharedCastPath();
 
 	LingoState *getLingoState() { return _lingoState; };
 	uint32 frozenLingoStateCount() { return _frozenLingoStates.size(); };


Commit: 2690a238ce8c998a2423b1ddde09643528f69981
    https://github.com/scummvm/scummvm/commit/2690a238ce8c998a2423b1ddde09643528f69981
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Add extensions if required when loading audio files

Fixes several sound effects in Hamsterland: The Time Machine.

Changed paths:
    engines/director/sound.cpp
    engines/director/util.cpp
    engines/director/util.h


diff --git a/engines/director/sound.cpp b/engines/director/sound.cpp
index eca3cb930f9..7eba28030f7 100644
--- a/engines/director/sound.cpp
+++ b/engines/director/sound.cpp
@@ -852,7 +852,7 @@ Audio::AudioStream *AudioFileDecoder::getAudioStream(bool looping, bool forPuppe
 	if (_path.empty())
 		return nullptr;
 
-	Common::Path newPath = findPath(_path);
+	Common::Path newPath = findAudioPath(_path);
 	Common::SeekableReadStream *copiedStream = Common::MacResManager::openFileOrDataFork(newPath);
 	if (!copiedStream) {
 		warning("Failed to open %s", _path.c_str());
diff --git a/engines/director/util.cpp b/engines/director/util.cpp
index 46b68526651..d5eded488ab 100644
--- a/engines/director/util.cpp
+++ b/engines/director/util.cpp
@@ -799,6 +799,26 @@ Common::Path findMoviePath(Common::String &path, bool currentFolder, bool search
 	return result;
 }
 
+Common::Path findAudioPath(Common::String &path, bool currentFolder, bool searchPaths) {
+	Common::Path result = findPath(path, currentFolder, searchPaths, false);
+	if (!result.empty())
+		return result;
+
+	const char *exts[] = { ".AIF", ".WAV", nullptr };
+
+	Common::String fileBase = path;
+	if (hasExtension(fileBase))
+		fileBase = fileBase.substr(0, fileBase.size() - 4);
+
+	for (int i = 0; exts[i]; i++) {
+		Common::String newPath = fileBase + exts[i];
+
+		result = findPath(newPath, currentFolder, searchPaths, false);
+		if (!result.empty())
+			break;
+	}
+	return result;
+}
 
 bool hasExtension(Common::String filename) {
 	uint len = filename.size();
diff --git a/engines/director/util.h b/engines/director/util.h
index df31c72ea67..043fd0f8b48 100644
--- a/engines/director/util.h
+++ b/engines/director/util.h
@@ -48,6 +48,7 @@ Common::FSNode resolvePartialPathWithFuzz(Common::String &path, Common::FSNode &
 Common::Path nodeToPath(Common::FSNode &node);
 Common::Path findPath(Common::String &path, bool currentFolder = true, bool searchPaths = true, bool directory = false);
 Common::Path findMoviePath(Common::String &path, bool currentFolder = true, bool searchPaths = true);
+Common::Path findAudioPath(Common::String &path, bool currentFolder = true, bool searchPaths = true);
 
 
 bool hasExtension(Common::String filename);


Commit: 0bcf80e400d223e582618237fea2ebc297fdd169
    https://github.com/scummvm/scummvm/commit/0bcf80e400d223e582618237fea2ebc297fdd169
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Fix detection entry for planetarizona

Changed paths:
    engines/director/detection_tables.h


diff --git a/engines/director/detection_tables.h b/engines/director/detection_tables.h
index b79806468c0..44c39e48959 100644
--- a/engines/director/detection_tables.h
+++ b/engines/director/detection_tables.h
@@ -5017,7 +5017,7 @@ static const DirectorGameDescription gameDescriptions[] = {
 	WINGAME1t_l("pippi", "v1.3.3", "PIPPI.EXE", "2f3533b95406015d03d5dcd8ba7d961f", 691043, Common::EN_ANY, 404),
 
 	MACGAME1("planetarizona", "", "Manual Installation/Planet Arizona", "0c7bbb4b24823e5ab871cb4c1d6f3710", 488433, 400),
-	WINGAME1("planetarizona", "", "ARIZONA.EXE", "a23462ec87eff973d2cdb2ddfd4a59fa", 698651, 400),
+	WINGAME1("planetarizona", "", "INSTALL/ARIZONA.EXE", "a23462ec87eff973d2cdb2ddfd4a59fa", 698651, 400),
 
 	// Mac demo from MacFormat #44
 	// Win demo from Computer Buyer v17n68 (January 1997)


Commit: 1a84d4d94bb31a6201fc3a9974fb17f246677412
    https://github.com/scummvm/scummvm/commit/1a84d4d94bb31a6201fc3a9974fb17f246677412
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: XOBJ: Add stub for DirUtil

Changed paths:
  A engines/director/lingo/xlibs/dirutil.cpp
  A engines/director/lingo/xlibs/dirutil.h
    engines/director/lingo/lingo-object.cpp
    engines/director/module.mk


diff --git a/engines/director/lingo/lingo-object.cpp b/engines/director/lingo/lingo-object.cpp
index 9d92241363a..fd4b3e97087 100644
--- a/engines/director/lingo/lingo-object.cpp
+++ b/engines/director/lingo/lingo-object.cpp
@@ -44,6 +44,7 @@
 #include "director/lingo/xlibs/darkenscreen.h"
 #include "director/lingo/xlibs/developerStack.h"
 #include "director/lingo/xlibs/dialogsxobj.h"
+#include "director/lingo/xlibs/dirutil.h"
 #include "director/lingo/xlibs/dpwavi.h"
 #include "director/lingo/xlibs/dpwqtw.h"
 #include "director/lingo/xlibs/draw.h"
@@ -173,6 +174,7 @@ static struct XLibProto {
 	{ DarkenScreen::fileNames,			DarkenScreen::open,			DarkenScreen::close,		kXObj,					300 },	// D3
 	{ DeveloperStack::fileNames,		DeveloperStack::open,		DeveloperStack::close,		kXObj,					300 },	// D3
 	{ DialogsXObj::fileNames,			DialogsXObj::open,			DialogsXObj::close,			kXObj,					400 },	// D4
+	{ DirUtilXObj::fileNames,			DirUtilXObj::open,			DirUtilXObj::close,			kXObj,					400 },	// D4
 	{ DPwAVI::fileNames,				DPwAVI::open,				DPwAVI::close,				kXObj,					400 },	// D4
 	{ DPwQTw::fileNames,				DPwQTw::open,				DPwQTw::close,				kXObj,					400 },	// D4
 	{ DrawXObj::fileNames,				DrawXObj::open,				DrawXObj::close,			kXObj,					400 },	// D4
diff --git a/engines/director/lingo/xlibs/dirutil.cpp b/engines/director/lingo/xlibs/dirutil.cpp
new file mode 100644
index 00000000000..caf2eb71b20
--- /dev/null
+++ b/engines/director/lingo/xlibs/dirutil.cpp
@@ -0,0 +1,120 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/system.h"
+
+#include "director/director.h"
+#include "director/lingo/lingo.h"
+#include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
+#include "director/lingo/xlibs/dirutil.h"
+
+/*
+-- DIRUTIL External Factory. 16Feb93 PTM
+DIRUTIL
+I      mNew                    --Creates a new instance of the XObject
+X      mDispose                --Disposes of XObject instance
+S      mName                   --Returns the XObject name (dirutil)
+I      mStatus                 --Returns an integer status code
+SI     mError, code            --Returns an error string
+S      mLastError              --Returns last error string
+ISI    mSetAttrib,filename,attrib --Sets the Attibute of attr
+IS     mGetAttrib,filename     --Gets the Attibute of filename
+II     mGetDriveType,drivenumber --Gets the type of a drive selected
+I      mQTVersion              --Returns an integer status code
+S      mQTVersionStr           --Returns version as a string
+II     mIsCdrom,drivenumber    --Return true if drive CDROM
+S      mAuthors                --Information on authors
+XI     mSetErrorMode,mode      --sets windoze error mode
+ */
+
+namespace Director {
+
+const char *DirUtilXObj::xlibName = "dirutil";
+const char *DirUtilXObj::fileNames[] = {
+	"dirutil",
+	nullptr
+};
+
+static MethodProto xlibMethods[] = {
+	{ "new",				DirUtilXObj::m_new,		 0, 0,	400 },	// D4
+	{ "dispose",				DirUtilXObj::m_dispose,		 0, 0,	400 },	// D4
+	{ "name",				DirUtilXObj::m_name,		 0, 0,	400 },	// D4
+	{ "status",				DirUtilXObj::m_status,		 0, 0,	400 },	// D4
+	{ "error",				DirUtilXObj::m_error,		 1, 1,	400 },	// D4
+	{ "lastError",				DirUtilXObj::m_lastError,		 0, 0,	400 },	// D4
+	{ "setAttrib",				DirUtilXObj::m_setAttrib,		 2, 2,	400 },	// D4
+	{ "getAttrib",				DirUtilXObj::m_getAttrib,		 1, 1,	400 },	// D4
+	{ "getDriveType",				DirUtilXObj::m_getDriveType,		 1, 1,	400 },	// D4
+	{ "qTVersion",				DirUtilXObj::m_qTVersion,		 0, 0,	400 },	// D4
+	{ "qTVersionStr",				DirUtilXObj::m_qTVersionStr,		 0, 0,	400 },	// D4
+	{ "isCdrom",				DirUtilXObj::m_isCdrom,		 1, 1,	400 },	// D4
+	{ "authors",				DirUtilXObj::m_authors,		 0, 0,	400 },	// D4
+	{ "setErrorMode",				DirUtilXObj::m_setErrorMode,		 1, 1,	400 },	// D4
+	{ nullptr, nullptr, 0, 0, 0 }
+};
+
+DirUtilXObject::DirUtilXObject(ObjectType ObjectType) :Object<DirUtilXObject>("DirUtilXObj") {
+	_objType = ObjectType;
+}
+
+void DirUtilXObj::open(int type) {
+	if (type == kXObj) {
+		DirUtilXObject::initMethods(xlibMethods);
+		DirUtilXObject *xobj = new DirUtilXObject(kXObj);
+		g_lingo->exposeXObject(xlibName, xobj);
+	} else if (type == kXtraObj) {
+		// TODO - Implement Xtra
+	}
+}
+
+void DirUtilXObj::close(int type) {
+	if (type == kXObj) {
+		DirUtilXObject::cleanupMethods();
+		g_lingo->_globalvars[xlibName] = Datum();
+	} else if (type == kXtraObj) {
+		// TODO - Implement Xtra
+	}
+}
+
+void DirUtilXObj::m_new(int nargs) {
+	if (nargs != 0) {
+		warning("DirUtilXObj::m_new: expected 0 arguments");
+		g_lingo->dropStack(nargs);
+	}
+	g_lingo->push(g_lingo->_state->me);
+}
+
+XOBJSTUBNR(DirUtilXObj::m_dispose)
+XOBJSTUB(DirUtilXObj::m_name, "")
+XOBJSTUB(DirUtilXObj::m_status, 0)
+XOBJSTUB(DirUtilXObj::m_error, "")
+XOBJSTUB(DirUtilXObj::m_lastError, "")
+XOBJSTUB(DirUtilXObj::m_setAttrib, 0)
+XOBJSTUB(DirUtilXObj::m_getAttrib, 0)
+XOBJSTUB(DirUtilXObj::m_getDriveType, 0)
+XOBJSTUB(DirUtilXObj::m_qTVersion, 0)
+XOBJSTUB(DirUtilXObj::m_qTVersionStr, "2.03.51")
+XOBJSTUB(DirUtilXObj::m_isCdrom, 0)
+XOBJSTUB(DirUtilXObj::m_authors, "")
+XOBJSTUBNR(DirUtilXObj::m_setErrorMode)
+
+}
diff --git a/engines/director/lingo/xlibs/dirutil.h b/engines/director/lingo/xlibs/dirutil.h
new file mode 100644
index 00000000000..56e7e0b9a9c
--- /dev/null
+++ b/engines/director/lingo/xlibs/dirutil.h
@@ -0,0 +1,59 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef DIRECTOR_LINGO_XLIBS_DIRUTIL_H
+#define DIRECTOR_LINGO_XLIBS_DIRUTIL_H
+
+namespace Director {
+
+class DirUtilXObject : public Object<DirUtilXObject> {
+public:
+	DirUtilXObject(ObjectType objType);
+};
+
+namespace DirUtilXObj {
+
+extern const char *xlibName;
+extern const char *fileNames[];
+
+void open(int type);
+void close(int type);
+
+void m_new(int nargs);
+void m_dispose(int nargs);
+void m_name(int nargs);
+void m_status(int nargs);
+void m_error(int nargs);
+void m_lastError(int nargs);
+void m_setAttrib(int nargs);
+void m_getAttrib(int nargs);
+void m_getDriveType(int nargs);
+void m_qTVersion(int nargs);
+void m_qTVersionStr(int nargs);
+void m_isCdrom(int nargs);
+void m_authors(int nargs);
+void m_setErrorMode(int nargs);
+
+} // End of namespace DirUtilXObj
+
+} // End of namespace Director
+
+#endif
diff --git a/engines/director/module.mk b/engines/director/module.mk
index 0300bc890e0..e5d003016f0 100644
--- a/engines/director/module.mk
+++ b/engines/director/module.mk
@@ -64,6 +64,7 @@ MODULE_OBJS = \
 	lingo/xlibs/darkenscreen.o \
 	lingo/xlibs/developerStack.o \
 	lingo/xlibs/dialogsxobj.o \
+	lingo/xlibs/dirutil.o \
 	lingo/xlibs/dpwavi.o \
 	lingo/xlibs/dpwqtw.o \
 	lingo/xlibs/draw.o \


Commit: 343e3af14fd135244d2d9b4abed9e54275c1469d
    https://github.com/scummvm/scummvm/commit/343e3af14fd135244d2d9b4abed9e54275c1469d
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Change Datum::asInt/asFloat behaviour for strings

In real Director, performing arithmetic or comparisons
between strings and numbers is allowed. First the string will be coerced
with strtof/strtod; if this works the value is used, if it doesn't work
then a number that appears to be the memory pointer is used.

In addition, b_integer and b_float need to be adjusted, as these methods
will also coerce with strtof/strtod but return a different value if they do not work.

Fixes the RNG seeding in Hamsterland: The Time Machine.

Changed paths:
    engines/director/lingo/lingo-builtins.cpp
    engines/director/lingo/lingo-code.cpp
    engines/director/lingo/lingo.cpp
    engines/director/lingo/lingo.h
    engines/director/lingo/tests/equality.lingo
    engines/director/lingo/tests/strings.lingo


diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp
index 13b9e4b11f4..8ad8aea0150 100644
--- a/engines/director/lingo/lingo-builtins.cpp
+++ b/engines/director/lingo/lingo-builtins.cpp
@@ -383,7 +383,22 @@ void LB::b_exp(int nargs) {
 
 void LB::b_float(int nargs) {
 	Datum d = g_lingo->pop();
-	Datum res(d.asFloat());
+	Datum res;
+
+	if (d.type == STRING) {
+		Common::String src = d.asString();
+		char *endPtr = nullptr;
+		double result = strtod(src.c_str(), &endPtr);
+		if (*endPtr == 0) {
+			res = result;
+		} else {
+			// for some reason, float(str) will return str if it doesn't work
+			res = d;
+		}
+	} else {
+		res = d.asFloat();
+	}
+
 	g_lingo->push(res);
 }
 
@@ -397,6 +412,13 @@ void LB::b_integer(int nargs) {
 		} else {
 			res = (int)round(d.u.f);
 		}
+	} else if (d.type == STRING) {
+		Common::String src = d.asString();
+		char *endPtr = nullptr;
+		int result = (int)strtol(src.c_str(), &endPtr, 10);
+		if (endPtr && endPtr != src.c_str() && (*endPtr == '\0' || *endPtr == ' ')) {
+			res = result;
+		}
 	} else {
 		res = d.asInt();
 	}
diff --git a/engines/director/lingo/lingo-code.cpp b/engines/director/lingo/lingo-code.cpp
index c28f7c20142..8bb595840f4 100644
--- a/engines/director/lingo/lingo-code.cpp
+++ b/engines/director/lingo/lingo-code.cpp
@@ -709,7 +709,7 @@ Datum LC::addData(Datum &d1, Datum &d2) {
 		return LC::mapBinaryOp(LC::addData, d1, d2);
 	}
 
-	int alignedType = g_lingo->getAlignedType(d1, d2, true);
+	int alignedType = g_lingo->getAlignedType(d1, d2, false);
 
 	Datum res;
 	if (alignedType == FLOAT) {
@@ -733,7 +733,7 @@ Datum LC::subData(Datum &d1, Datum &d2) {
 		return LC::mapBinaryOp(LC::subData, d1, d2);
 	}
 
-	int alignedType = g_lingo->getAlignedType(d1, d2, true);
+	int alignedType = g_lingo->getAlignedType(d1, d2, false);
 
 	Datum res;
 	if (alignedType == FLOAT) {
@@ -757,7 +757,7 @@ Datum LC::mulData(Datum &d1, Datum &d2) {
 		return LC::mapBinaryOp(LC::mulData, d1, d2);
 	}
 
-	int alignedType = g_lingo->getAlignedType(d1, d2, true);
+	int alignedType = g_lingo->getAlignedType(d1, d2, false);
 
 	Datum res;
 	if (alignedType == FLOAT) {
@@ -787,7 +787,7 @@ Datum LC::divData(Datum &d1, Datum &d2) {
 		d2 = Datum(1);
 	}
 
-	int alignedType = g_lingo->getAlignedType(d1, d2, true);
+	int alignedType = g_lingo->getAlignedType(d1, d2, false);
 
 	if (g_director->getVersion() < 400)	// pre-D4 is INT-only
 		alignedType = INT;
diff --git a/engines/director/lingo/lingo.cpp b/engines/director/lingo/lingo.cpp
index db0f39c810b..ad339c51a18 100644
--- a/engines/director/lingo/lingo.cpp
+++ b/engines/director/lingo/lingo.cpp
@@ -743,14 +743,16 @@ void Lingo::resetLingo() {
 	resetLingoGo();
 }
 
-int Lingo::getAlignedType(const Datum &d1, const Datum &d2, bool numsOnly) {
+int Lingo::getAlignedType(const Datum &d1, const Datum &d2, bool equality) {
 	int opType = VOID;
 
 	int d1Type = d1.type;
 	int d2Type = d2.type;
 
-	if (d1Type == d2Type && (!numsOnly || d1Type == INT || d1Type == FLOAT))
-		return d1Type;
+	if (equality) {
+		if (d1Type == STRING && d2Type == STRING)
+			return STRING;
+	}
 
 	if (d1Type == STRING) {
 		Common::String src = d1.asString();
@@ -759,7 +761,11 @@ int Lingo::getAlignedType(const Datum &d1, const Datum &d2, bool numsOnly) {
 			strtod(src.c_str(), &endPtr);
 			if (*endPtr == 0) {
 				d1Type = FLOAT;
+			} else if (!equality) {
+				d1Type = INT;
 			}
+		} else {
+			d1Type = VOID;
 		}
 	}
 	if (d2Type == STRING) {
@@ -769,7 +775,11 @@ int Lingo::getAlignedType(const Datum &d1, const Datum &d2, bool numsOnly) {
 			strtod(src.c_str(), &endPtr);
 			if (*endPtr == 0) {
 				d2Type = FLOAT;
+			} else if (!equality) {
+				d2Type = INT;
 			}
+		} else {
+			d2Type = VOID;
 		}
 	}
 
@@ -783,14 +793,12 @@ int Lingo::getAlignedType(const Datum &d1, const Datum &d2, bool numsOnly) {
 	if (d2Type == OBJECT)
 		d2Type = STRING;
 
-	if (d1Type == FLOAT || d2Type == FLOAT) {
+	if ((d1Type == FLOAT && d2Type == INT) || (d1Type == INT && d2Type == FLOAT)) {
 		opType = FLOAT;
-	} else if (d1Type == INT && d2Type == INT) {
-		opType = INT;
 	} else if ((d1Type == STRING && d2Type == INT) || (d1Type == INT && d2Type == STRING)) {
 		opType = STRING;
-	} else if ((d1Type == STRING && d2Type == SYMBOL) || (d1Type == SYMBOL && d2Type == STRING)) {
-		opType = STRING;
+	} else if (d1Type == d2Type) {
+		opType = d1Type;
 	}
 
 	return opType;
@@ -963,11 +971,12 @@ int Datum::asInt() const {
 		{
 			Common::String src = asString();
 			char *endPtr = nullptr;
-			int result = strtol(src.c_str(), &endPtr, 10);
+			float result = strtof(src.c_str(), &endPtr);
 			if (*endPtr == 0) {
-				res = result;
+				res = (int)result;
 			} else {
-				warning("Invalid int '%s'", src.c_str());
+				warning("Invalid number '%s'", src.c_str());
+				res = (int)((uint64)u.s & 0xffffffffL);
 			}
 		}
 		break;
@@ -1002,7 +1011,8 @@ double Datum::asFloat() const {
 			if (*endPtr == 0) {
 				res = result;
 			} else {
-				warning("Invalid float '%s'", src.c_str());
+				warning("Invalid number '%s'", src.c_str());
+				res = (int)((uint64)u.s & 0xffffffffL);
 			}
 		}
 		break;
@@ -1261,7 +1271,7 @@ const char *Datum::type2str(bool ilk) const {
 }
 
 int Datum::equalTo(Datum &d, bool ignoreCase) const {
-	int alignType = g_lingo->getAlignedType(*this, d, false);
+	int alignType = g_lingo->getAlignedType(*this, d, true);
 
 	switch (alignType) {
 	case FLOAT:
@@ -1310,7 +1320,7 @@ bool Datum::operator<=(Datum &d) const {
 }
 
 CompareResult Datum::compareTo(Datum &d) const {
-	int alignType = g_lingo->getAlignedType(*this, d, false);
+	int alignType = g_lingo->getAlignedType(*this, d, true);
 
 	if (alignType == FLOAT) {
 		double f1 = asFloat();
diff --git a/engines/director/lingo/lingo.h b/engines/director/lingo/lingo.h
index 6af4c1aa02a..9f78c94023c 100644
--- a/engines/director/lingo/lingo.h
+++ b/engines/director/lingo/lingo.h
@@ -394,7 +394,7 @@ public:
 	CastMemberID resolveCastMember(const Datum &memberID, const Datum &castLib, CastType type);
 	void exposeXObject(const char *name, Datum obj);
 
-	int getAlignedType(const Datum &d1, const Datum &d2, bool numsOnly);
+	int getAlignedType(const Datum &d1, const Datum &d2, bool equality);
 
 	Common::String formatAllVars();
 	void printAllVars();
diff --git a/engines/director/lingo/tests/equality.lingo b/engines/director/lingo/tests/equality.lingo
index 60c4b45dafe..25d429017e7 100644
--- a/engines/director/lingo/tests/equality.lingo
+++ b/engines/director/lingo/tests/equality.lingo
@@ -6,13 +6,39 @@ scummvmAssert("test" = "test")
 scummvmAssert(#test = #test)
 set string = the text of field 1
 scummvmAssert(field 1 = string)
-scummvmAssert(0 <> "")
+scummvmAssert(0 = "")
 scummvmAssert(1 <> cast 1)
 scummvmAssert("test" <> #test)
 
--- Comparison between string and ints parses the int as string
-scummvmAssert("string" > 0)
-scummvmAssert("1string" < 2)
+-- If string parses as a float, coerce to float
+scummvmAssert("2000" > 25)
+scummvmAssert(25 < "2000")
+scummvmAssert("2000.5" > 25)
+scummvmAssert(25 < "2000.5")
+scummvmAssert("20e5" > 25)
+scummvmAssert(25 < "20e5")
+scummvmAssert("      2000" > 25)
+scummvmAssert(25 < "      2000")
+scummvmAssert("2000" > 2.5)
+scummvmAssert(25 < "2000")
+scummvmAssert("2000.5" > 2.5)
+scummvmAssert(2.5 < "2000.5")
+scummvmAssert("20e5" > 2.5)
+scummvmAssert(2.5 < "20e5")
+scummvmAssert("      2000" > 2.5)
+scummvmAssert(2.5 < "      2000")
+
+-- If a string doesn't parse as a float, coerce the number to a string
+scummvmAssert("2/" < 20)
+scummvmAssert(20 > "2/")
+scummvmAssert("2000 e" < 25)
+scummvmAssert(25 > "2000 e")
+
+-- Two strings, treat with normal string ordering rules
+scummvmAssert("2000" <> "2000.0")
+scummvmAssert("2000" < "25")
+scummvmAssert("abc" < "abcd")
+scummvmAssert("abc" < "def")
 
 -- Mimic an object
 scummvmAssert("<Object:#FileIO" > 0)
@@ -21,9 +47,9 @@ scummvmAssert("<Object:#FileIO" > 0)
 scummvmAssert(not (#test <= 0))
 
 -- Picture comparisons are always false, even between the exact same cast.
-set a to the picture of cast 1
-scummvmAssert(a <> a)
-scummvmAssert(a <> the picture of cast 1)   -- always false
+-- set a to the picture of cast 1
+-- scummvmAssert(a <> a)
+-- scummvmAssert(a <> the picture of cast 1)   -- always false
 
 -- String comparison
 scummvmAssert("a" > "A")
@@ -32,9 +58,3 @@ scummvmAssert("a" <= "Bubba")
 scummvmAssert("z" > "Z")
 scummvmAssert("abba" > "Abba")
 
--- This behaviour was fixed by 8.5
-set save to the scummvmVersion
-set the scummvmVersion to 850
-scummvmAssert("a" <= "Z")
-scummvmAssert("a" <= "A")
-set the scummvmVersion to save
diff --git a/engines/director/lingo/tests/strings.lingo b/engines/director/lingo/tests/strings.lingo
index 5b7b0ff62e8..36f32e612c5 100644
--- a/engines/director/lingo/tests/strings.lingo
+++ b/engines/director/lingo/tests/strings.lingo
@@ -36,6 +36,88 @@ scummvmAssertEqual(test, return & "foo" & return)
 put return into test
 scummvmAssertEqual(test, return)
 
+-- coercing strings to numbers
+-- str(int) + int
+scummvmAssertEqual("2" + 5, 7.0)
+scummvmAssertEqual(5 + "2", 7.0)
+-- str(int) + float
+scummvmAssertEqual("2" + 5.5, 7.5)
+scummvmAssertEqual(5.5 + "2", 7.5)
+-- str(float) + int
+scummvmAssertEqual("2.5" + 5, 7.5)
+scummvmAssertEqual(5 + "2.5", 7.5)
+-- str(float) + float
+scummvmAssertEqual("2.25" + 5.5, 7.75)
+scummvmAssertEqual(5.5 + "2.25", 7.75)
+
+-- str(int) + str(int)
+scummvmAssertEqual("2" + "5", 7.0)
+scummvmAssertEqual("5" + "2", 7.0)
+-- str(int) + str(float)
+scummvmAssertEqual("2" + "5.5", 7.5)
+scummvmAssertEqual("5.5" + "2", 7.5)
+-- str(float) + str(int)
+scummvmAssertEqual("2.5" + "5", 7.5)
+scummvmAssertEqual("5" + "2.5", 7.5)
+-- str(float) + str(float)
+scummvmAssertEqual("2.25" + "5.5", 7.75)
+scummvmAssertEqual("5.5" + "2.25", 7.75)
+
+
+
+-- float syntax
+scummvmAssertEqual("-2" + 5, 3.0)
+scummvmAssertEqual("-2.5" + 5, 2.5)
+scummvmAssertEqual("+2" + 5, 7.0)
+scummvmAssertEqual("+2.5" + 5, 7.5)
+scummvmAssertEqual("2e3" + 5, 2005.0)
+scummvmAssertEqual("2.5e3" + 5, 2505.0)
+scummvmAssertEqual("    2" + 5, 7.0)
+scummvmAssertEqual("    2.5" + 5, 7.5)
+
+-- non number strings should coerce to a pointer
+scummvmAssert("incorrect" + 5 > 10000)
+scummvmAssert("    2.5     " + 5 > 10000)
+scummvmAssert("2 uhhh" + 5 > 10000)
+scummvmAssert("2.5 uhhh" + 5 > 10000)
+
+-- casting to integer
+scummvmAssertEqual(integer("2"), 2)
+scummvmAssertEqual(integer("-2"), -2)
+scummvmAssertEqual(integer("     2"), 2)
+scummvmAssertEqual(integer("2.5"), VOID)
+scummvmAssertEqual(integer("     2.5"), VOID)
+scummvmAssertEqual(integer("incorrect"), VOID)
+scummvmAssertEqual(integer("2 extra"), 2)
+scummvmAssertEqual(integer("2.5 extra"), VOID)
+scummvmAssertEqual(integer("     2 extra"), 2)
+scummvmAssertEqual(integer("     2.5 extra"), VOID)
+scummvmAssertEqual(integer("2extra"), VOID)
+scummvmAssertEqual(integer("     2extra"), VOID)
+
+-- casting to float
+scummvmAssertEqual(float("2"), 2.0)
+scummvmAssertEqual(float("-2"), -2.0)
+scummvmAssertEqual(float("     2"), 2.0)
+scummvmAssertEqual(float("2.5"), 2.5)
+scummvmAssertEqual(float("     2.5"), 2.5)
+scummvmAssertEqual(float("incorrect"), "incorrect")
+scummvmAssertEqual(float("2 extra"), "2 extra")
+scummvmAssertEqual(float("2.5 extra"), "2.5 extra")
+scummvmAssertEqual(float("     2 extra"), "     2 extra")
+scummvmAssertEqual(float("     2.5 extra"), "     2.5 extra")
+scummvmAssertEqual(float("2extra"), "2extra")
+scummvmAssertEqual(float("     2extra"), "     2extra")
+
+put "sausages" into testString
+put (testString + 0) into testPointer
+scummvmAssertEqual(testPointer > 10000, TRUE)
+scummvmAssertEqual(testString + 4, testPointer + 4)
+scummvmAssertEqual(testString - 4, testPointer - 4)
+scummvmAssertEqual(testString * 4, testPointer * 4)
+scummvmAssertEqual(testString / 4, testPointer / 4)
+
+
 -- LC::charOF
 set string to "Macromedia"
 set res to char 6 of string


Commit: d7b779f0853f9c5b4b14799be22d956e7ffa54b0
    https://github.com/scummvm/scummvm/commit/d7b779f0853f9c5b4b14799be22d956e7ffa54b0
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Add devtool for generating XObject/XLib stubs

Changed paths:
  A devtools/director-generate-xobj-stub.py


diff --git a/devtools/director-generate-xobj-stub.py b/devtools/director-generate-xobj-stub.py
new file mode 100755
index 00000000000..b155472083e
--- /dev/null
+++ b/devtools/director-generate-xobj-stub.py
@@ -0,0 +1,337 @@
+#!/usr/bin/env python
+
+import argparse
+import os
+import re
+import struct
+from typing import BinaryIO
+
+DIRECTOR_SRC_PATH = os.path.abspath(os.path.join(__file__, "..", "engines", "director"))
+MAKEFILE_PATH = os.path.join(DIRECTOR_SRC_PATH, "module.mk")
+LINGO_XLIBS_PATH = os.path.join(DIRECTOR_SRC_PATH, "lingo", "xlibs")
+LINGO_OBJECT_PATH = os.path.join(DIRECTOR_SRC_PATH, "lingo", "lingo-object.cpp")
+
+TEMPLATE_H = """/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef DIRECTOR_LINGO_XLIBS_{slug_upper}_H
+#define DIRECTOR_LINGO_XLIBS_{slug_upper}_H
+
+namespace Director {{
+
+class {xobject_class} : public Object<{xobject_class}> {{
+public:
+	{xobject_class}(ObjectType objType);
+}};
+
+namespace {xobj_class} {{
+
+extern const char *xlibName;
+extern const char *fileNames[];
+
+void open(int type);
+void close(int type);
+
+{methlist}
+
+}} // End of namespace {xobj_class}
+
+}} // End of namespace Director
+
+#endif
+"""
+
+TEMPLATE_HEADER_METH = """void m_{methname}(int nargs);"""
+
+TEMPLATE = """/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/system.h"
+
+#include "director/director.h"
+#include "director/lingo/lingo.h"
+#include "director/lingo/lingo-object.h"
+#include "director/lingo/lingo-utils.h"
+#include "director/lingo/xlibs/{slug}.h"
+
+/**************************************************
+ *
+ * USED IN:
+ * [insert game here]
+ *
+ **************************************************/
+
+/*
+{xmethtable}
+ */
+
+namespace Director {{
+
+const char *{xobj_class}::xlibName = "{slug}";
+const char *{xobj_class}::fileNames[] = {{
+	"{slug}",
+	nullptr
+}};
+
+static MethodProto xlibMethods[] = {{
+{xlib_methods}
+	{{ nullptr, nullptr, 0, 0, 0 }}
+}};
+
+{xobject_class}::{xobject_class}(ObjectType ObjectType) :Object<{xobject_class}>("{xobj_class}") {{
+	_objType = ObjectType;
+}}
+
+void {xobj_class}::open(int type) {{
+	if (type == kXObj) {{
+		{xobject_class}::initMethods(xlibMethods);
+		{xobject_class} *xobj = new {xobject_class}(kXObj);
+		g_lingo->exposeXObject(xlibName, xobj);
+	}} else if (type == kXtraObj) {{
+		// TODO - Implement Xtra
+	}}
+}}
+
+void {xobj_class}::close(int type) {{
+	if (type == kXObj) {{
+		{xobject_class}::cleanupMethods();
+		g_lingo->_globalvars[xlibName] = Datum();
+	}} else if (type == kXtraObj) {{
+		// TODO - Implement Xtra
+	}}
+}}
+
+void {xobj_class}::m_new(int nargs) {{
+	if (nargs != 0) {{
+		warning("{xobj_class}::m_new: expected 0 arguments");
+		g_lingo->dropStack(nargs);
+	}}
+	g_lingo->push(g_lingo->_state->me);
+}}
+
+{xobj_stubs}
+
+}}
+"""
+XLIB_METHOD_TEMPLATE = """	{{ "{methname}",				{xobj_class}::m_{methname},		 {arg_count}, {arg_count},	{director_version} }},"""
+
+XOBJ_STUB_TEMPLATE = """XOBJSTUB({xobj_class}::m_{methname}, {default})"""
+
+XOBJ_NR_STUB_TEMPLATE = """XOBJSTUBNR({xobj_class}::m_{methname})"""
+
+def read_uint8(data: bytes) -> int:
+	return struct.unpack('B', data)[0]
+
+def read_uint16_le(data: bytes) -> int:
+	return struct.unpack('<H', data)[0]
+
+def extract_xmethtable_win16(file: BinaryIO, ne_offset: int) -> tuple[str, list[str]]:
+	# get resource table
+	file.seek(ne_offset + 0x24, os.SEEK_SET)
+	restable_offset = read_uint16_le(file.read(0x2))
+	file.seek(ne_offset + restable_offset)
+	shift_count = read_uint16_le(file.read(0x2))
+	# read each resource
+	resources = []
+	while True:
+		type_id = read_uint16_le(file.read(0x2)) # should be 0x800a for XMETHTABLE
+		if type_id == 0:
+			break
+		count = read_uint16_le(file.read(0x2))
+		file.read(0x4) # reserved
+		entries = []
+		for i in range(count):
+			file_offset = read_uint16_le(file.read(0x2))
+			file_length = read_uint16_le(file.read(0x2))
+			entries.append(dict(
+				offset=file_offset << shift_count,
+				length=file_length << shift_count
+			))
+			file.read(0x2) # flagword
+			file.read(0x2) # resource_id
+			file.read(0x2) # handle
+			file.read(0x2) # usage
+		resources.append(dict(
+			type_id=type_id,
+			entries=entries
+		))
+	resource_names = []
+	while True:
+		length = read_uint8(file.read(0x1))
+		if length == 0:
+			break
+		resource_names.append(file.read(length).decode('ASCII'))
+
+	print("Resources found:")
+	print(resources, resource_names)
+
+	xmethtable_exists = "XMETHTABLE" in resource_names
+	resource_names = [x for x in resource_names if x != "XMETHTABLE"]
+
+	# Borland C++ can put the XMETHTABLE token into a weird nonstandard resource
+	for x in filter(lambda d: d["type_id"] == 0x800f, resources):
+		for y in x["entries"]:
+			file.seek(y["offset"], os.SEEK_SET)
+			data = file.read(y["length"])
+			xmethtable_exists |= b"XMETHTABLE" in data
+
+	if not xmethtable_exists:
+		raise ValueError("XMETHTABLE not found in the resource table!")
+
+	if len(resource_names) == 0:
+		raise ValueError(f"No names in the resource table!")
+
+	resources = list(filter(lambda x: x["type_id"] == 0x800a, resources))
+	if len(resources) != 1:
+		raise ValueError("Expected a single matching resource type entry!")
+	if len(resources[0]["entries"]) != 1:
+		raise ValueError("Expected a single matching resource entry!")
+
+	library_name = resource_names[0]
+	xmethtable_offset = resources[0]["entries"][0]["offset"]
+	xmethtable_length = resources[0]["entries"][0]["length"]
+	print(f"Found XMETHTABLE for XObject library {library_name}!")
+	file.seek(xmethtable_offset, os.SEEK_SET)
+	xmethtable_raw = file.read(xmethtable_length)
+	xmethtable = [entry.decode('iso-8859-1') for entry in xmethtable_raw.strip(b"\x00").split(b"\x00")]
+	for entry in xmethtable:
+		print(entry)
+	return library_name, xmethtable
+
+
+def extract_xmethtable(path: str):
+	with open(path, 'rb') as file:
+		magic = file.read(0x2)
+		if magic == b'MZ':
+			file.seek(0x3c, os.SEEK_SET)
+			header_offset = read_uint16_le(file.read(0x2))
+			file.seek(header_offset, os.SEEK_SET)
+			magic = file.read(0x2)
+			if magic == b'NE':
+				print("Found Win16 NE DLL!")
+				return extract_xmethtable_win16(file, header_offset)
+			elif magic == b'PE':
+				raise ValueError("No support yet for extracting from Win32 DLLs")
+	raise ValueError("Unknown filetype")
+
+
+def generate_stubs(xmethtable: list[str], slug: str, name: str, director_version: int=400, dry_run=False) -> None:
+	entries = xmethtable[2:]
+
+	meths = []
+	for e in entries:
+		if not e.strip():
+			break
+		elems = e.split()
+		returnval = elems[0][0]
+		args = elems[0][1:]
+		methname = elems[1].split(",")[0]
+		if methname.startswith('m'):
+			methname = methname[1].lower() + methname[2:]
+		meths.append(dict(
+			methname=methname,
+			args=args,
+			arg_count=len(args),
+			returnval=returnval,
+			default= '""' if returnval == "S" else "0"
+		))
+
+	xobject_class = f"{name}XObject"
+	xobj_class = f"{name}XObj"
+
+	cpp_text = TEMPLATE.format(
+		slug=slug,
+		xmethtable="\n".join(xmethtable),
+
+		xobject_class=xobject_class,
+		xobj_class=xobj_class,
+		xlib_methods="\n".join([XLIB_METHOD_TEMPLATE.format(
+			xobj_class=xobj_class,
+			director_version=director_version,
+			**x
+		) for x in meths]),
+		xobj_stubs="\n".join([
+			XOBJ_NR_STUB_TEMPLATE.format(xobj_class=xobj_class, **x)
+			if x["returnval"] == "X" else
+			XOBJ_STUB_TEMPLATE.format(xobj_class=xobj_class, **x)
+			for x in meths if x["methname"] != "new"
+		]),
+	)
+	if dry_run:
+		print("C++ output:")
+		print(cpp_text)
+		print()
+	else:
+		with open(os.path.join(LINGO_XLIBS_PATH, f"{slug}.cpp"), "w") as cpp:
+			cpp.write(cpp_text)
+
+	header_text = TEMPLATE_H.format(
+		slug_upper=slug.upper(),
+		xobject_class=f"{name}XObject",
+		xobj_class=f"{name}XObj",
+		methlist="\n".join([TEMPLATE_HEADER_METH.format(**x) for x in meths]),
+	)
+	if dry_run:
+		print("Header output:")
+		print(header_text)
+		print()
+	else:
+		with open(os.path.join(LINGO_XLIBS_PATH, f"{slug}.h"), "w") as header:
+			header.write(header_text)
+
+
+def main() -> None:
+	parser = argparse.ArgumentParser(
+		description="Extract the method table from a Macromedia Director XObject/XLib and generate method stubs."
+	)
+	parser.add_argument("XOBJ_FILE", help="XObject/XLib file to test")
+	parser.add_argument("--slug", help="Slug to use for files (e.g. {slug}.cpp, {slug}.h)")
+	parser.add_argument("--name", help="Base name to use for classes (e.g. {name}XObj, {name}XObject)")
+	parser.add_argument("--version", metavar="VER", help="Minimum Director version (default: 400)", default="400")
+	parser.add_argument("--dry-run", help="Test only, don't write files", action='store_true')
+	args = parser.parse_args()
+
+	library_name, xmethtable = extract_xmethtable(args.XOBJ_FILE)
+	slug = args.slug or library_name.lower()
+	name = args.name or xmethtable[1]
+	generate_stubs(xmethtable, slug, name, args.version, args.dry_run)
+
+
+if __name__ == "__main__":
+	main()


Commit: 34d962d95946bb8cb5389b57a3bb6b93483c2a92
    https://github.com/scummvm/scummvm/commit/34d962d95946bb8cb5389b57a3bb6b93483c2a92
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Add Gothos to game list

Changed paths:
    devtools/director-generate-xobj-stub.py
    engines/director/detection_tables.h


diff --git a/devtools/director-generate-xobj-stub.py b/devtools/director-generate-xobj-stub.py
index b155472083e..7103a9f16ef 100755
--- a/devtools/director-generate-xobj-stub.py
+++ b/devtools/director-generate-xobj-stub.py
@@ -235,6 +235,17 @@ def extract_xmethtable_win16(file: BinaryIO, ne_offset: int) -> tuple[str, list[
 	return library_name, xmethtable
 
 
+def extract_xmethtable_win32(file: BinaryIO, pe_offset: int) -> tuple[str, list[str]]:
+	# get the .data section
+	# find a string b"msgTable\x00", get the offset
+	# get the .text section
+	# find assembly:
+	# 68 [ addr ] 6a 00 6a [ addr 2 ]
+	# lookup addr2 in .data
+	# get c string, split by \x0a
+
+	return ("", [])
+
 def extract_xmethtable(path: str):
 	with open(path, 'rb') as file:
 		magic = file.read(0x2)
diff --git a/engines/director/detection_tables.h b/engines/director/detection_tables.h
index 44c39e48959..b9bc96c52b7 100644
--- a/engines/director/detection_tables.h
+++ b/engines/director/detection_tables.h
@@ -192,6 +192,7 @@ static const PlainGameDescriptor directorGames[] = {
 	{ "goferwinkel",		"Goferwinkel's Adventures: The Lavender Land" },
 	{ "goldilocks",			"Goldilocks Gamebook" },
 	{ "gordak",				"Gord at k" },
+	{ "gothos",				"Gothos" },
 	{ "grammarplaytime2",	"Grammar Playtime Vol.2: Asking Questions" },
 	{ "gundam0079",			"Gundam 0079: The War for Earth" },
 	{ "guscarn",			"Gus Goes to the Kooky Carnival" },
@@ -6063,6 +6064,9 @@ static const DirectorGameDescription gameDescriptions[] = {
 	WINGAME2t_l("glassyocean", "", "START.EXE",		  "c5dcc03aca7e0bed95844afdd86f866c", 1410529,
 								   "DATA/TAMURA.DXR", "a7bd15f6f42162fed7f32e0a739617ec", 155168, Common::JA_JPN, 501),
 
+	WINGAME1("gothos", "", "gothos.exe", "6199bb7cabc7f636394cacffd6a71fa9", 1788333, 501),
+	MACGAME1("gothos", "", "MACINST/GOTHOS", "1d08e56a4c7ba60a67417c7988cd3ffe", 718097, 501),
+
 	// Green Eggs and Hamulator mini-game
 	// Demo for a Living Books game that is supported in MOHAWK engine
 	// Found on Disc Inferno from APC magazine (Australia), Dec 1999


Commit: 4be2399b6b24d8496f1505e455f2d06f1753366d
    https://github.com/scummvm/scummvm/commit/4be2399b6b24d8496f1505e455f2d06f1753366d
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: LINGO: Make scummvmAssert crash only with kDebugLingoStrict

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


diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp
index 8ad8aea0150..3a9acf5daf2 100644
--- a/engines/director/lingo/lingo-builtins.cpp
+++ b/engines/director/lingo/lingo-builtins.cpp
@@ -3287,9 +3287,11 @@ void LB::b_scummvmassert(int nargs) {
 	Datum d = g_lingo->pop();
 
 	if (d.asInt() == 0) {
-		warning("LB::b_scummvmassert: is false at line %d", line.asInt());
+		warning("BUILDBOT: LB::b_scummvmassert: is false at line %d", line.asInt());
+	}
+	if (debugChannelSet(-1, kDebugLingoStrict)) {
+		assert(d.asInt() != 0);
 	}
-	assert(d.asInt() != 0);
 }
 
 void LB::b_scummvmassertequal(int nargs) {
@@ -3299,9 +3301,11 @@ void LB::b_scummvmassertequal(int nargs) {
 
 	int result = (d1 == d2);
 	if (!result) {
-		warning("LB::b_scummvmassertequals: %s is not equal %s at line %d", d1.asString().c_str(), d2.asString().c_str(), line.asInt());
+		warning("BUILDBOT: LB::b_scummvmassertequals: %s is not equal %s at line %d", d1.asString().c_str(), d2.asString().c_str(), line.asInt());
+	}
+	if (debugChannelSet(-1, kDebugLingoStrict)) {
+		assert(result == 1);
 	}
-	assert(result == 1);
 }
 
 void LB::b_getVolumes(int nargs) {


Commit: 17e43beb3da7131da5af31f42ea2596b720c933b
    https://github.com/scummvm/scummvm/commit/17e43beb3da7131da5af31f42ea2596b720c933b
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Add alias for RearWindow XObj

Changed paths:
    engines/director/lingo/xlibs/winxobj.cpp


diff --git a/engines/director/lingo/xlibs/winxobj.cpp b/engines/director/lingo/xlibs/winxobj.cpp
index f66ad50fe93..b7213bbf576 100644
--- a/engines/director/lingo/xlibs/winxobj.cpp
+++ b/engines/director/lingo/xlibs/winxobj.cpp
@@ -49,6 +49,7 @@ const char *RearWindowXObj::fileNames[] = {
 	"Backdrop",
 	"RearWindow",
 	"RearWindow.Xobj",
+	"RearWindow XObj",
 	"winXObj",
 	nullptr
 };


Commit: 3245ae99faabff234ecd256b2732dcacb42fe27c
    https://github.com/scummvm/scummvm/commit/3245ae99faabff234ecd256b2732dcacb42fe27c
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Rework resolvePath functions to pass Path instead of FSNode

Changed paths:
    engines/director/util.cpp
    engines/director/util.h


diff --git a/engines/director/util.cpp b/engines/director/util.cpp
index d5eded488ab..00dfcc14dbf 100644
--- a/engines/director/util.cpp
+++ b/engines/director/util.cpp
@@ -552,28 +552,37 @@ Common::String convert83Path(Common::String &path) {
 	return convPath;
 }
 
-Common::FSNode resolvePath(Common::String &path, Common::FSNode &base, bool directory) {
-	Common::FSNode d;
-	if (base.exists() && base.isDirectory()) {
-		d = base;
-	} else {
-		d = Common::FSNode(*g_director->getGameDataDir());
+Common::Path resolvePath(Common::String &path, Common::Path &base, bool directory) {
+	// Absolute path to the game directory
+	Common::Path gamePath = Common::Path(g_director->getGameDataDir()->getPath());
+	// Absolute path to the game directory + the base search path
+	Common::Path testPath = gamePath;
+	if (!base.empty()) {
+		testPath.appendInPlace(Common::String(g_director->_dirSeparator), g_director->_dirSeparator);
+		testPath.appendInPlace(base);
 	}
-	Common::FSNode node;
-	path = convertPath(path);
+	// FSNode for the current walk location in the filesystem
+	Common::FSNode filesystem(testPath);
 
+	// Path is the raw input from Director. Scrub it to be a clean relative path.
+	path = convertPath(path);
+	// Split this into a component list for iteration.
 	Common::StringTokenizer directory_list(path, Common::String(g_director->_dirSeparator));
-	Common::String newPath;
+	// newPath is our final result; construct this based on successful filesystem tests
+	Common::Path newPath = Common::Path(base);
+	if (!base.empty())
+		newPath.appendInPlace(Common::String(g_director->_dirSeparator), g_director->_dirSeparator);
 
 	Common::FSList fslist;
 	while (!directory_list.empty()) {
-		Common::String token = punycode_decodefilename(directory_list.nextToken());
+		Common::String token = directory_list.nextToken();
+		Common::String decodedToken = punycode_decodefilename(token);
 		fslist.clear();
 		Common::FSNode::ListMode mode = Common::FSNode::kListDirectoriesOnly;
 		if (directory_list.empty() && !directory) {
 			mode = Common::FSNode::kListAll;
 		}
-		bool hasChildren = d.getChildren(fslist, mode);
+		bool hasChildren = filesystem.getChildren(fslist, mode);
 		if (!hasChildren)
 			continue;
 
@@ -581,38 +590,37 @@ Common::FSNode resolvePath(Common::String &path, Common::FSNode &base, bool dire
 		for (auto &i : fslist) {
 			// for each element in the path, choose the first FSNode
 			// with a case-insensitive matching name
-			if (i.getName().equalsIgnoreCase(token)) {
-				node = Common::FSNode(i);
+			if (i.getName().equalsIgnoreCase(decodedToken)) {
 				// If this the final path component, check if we're allowed to match with a directory
-				if (directory_list.empty() && (directory != node.isDirectory())) {
+				if (directory_list.empty() && (directory != i.isDirectory())) {
 					continue;
 				}
 
 				exists = true;
-				newPath += i.getName();
-				if (!directory_list.empty())
-					newPath += (g_director->_dirSeparator);
+				newPath.appendInPlace(i.getName());
+				if (!directory_list.empty() && !newPath.empty())
+					newPath.appendInPlace(Common::String(g_director->_dirSeparator), g_director->_dirSeparator);
 
-				d = node;
+				filesystem = i;
 				break;
 			}
 		}
 		if (!exists) {
 			debugN(9, "%s", recIndent());
 			debug(9, "resolvePath(): No match found for %s", path.c_str());
-			return Common::FSNode();
+			return Common::Path();
 		}
 	}
 	debugN(9, "%s", recIndent());
-	debug(9, "resolvePath(): Found match for %s -> %s", path.c_str(), d.getPath().c_str());
-	return d;
+	debug(9, "resolvePath(): Found filesystem match for %s -> %s", path.c_str(), newPath.toString().c_str());
+	return newPath;
 }
 
-Common::FSNode resolvePartialPath(Common::String &path, Common::FSNode &base, bool directory) {
+Common::Path resolvePartialPath(Common::String &path, Common::Path &base, bool directory) {
 	path = convertPath(path);
 	Common::StringArray tokens = Common::StringTokenizer(path, Common::String(g_director->_dirSeparator)).split();
 
-	Common::FSNode result;
+	Common::Path result;
 	while (tokens.size()) {
 		Common::String subpath;
 		for (uint i = 0; i < tokens.size(); i++) {
@@ -622,7 +630,7 @@ Common::FSNode resolvePartialPath(Common::String &path, Common::FSNode &base, bo
 			}
 		}
 		result = resolvePath(subpath, base, directory);
-		if (result.exists()) {
+		if (!result.empty()) {
 			break;
 		}
 		tokens.remove_at(0);
@@ -630,15 +638,15 @@ Common::FSNode resolvePartialPath(Common::String &path, Common::FSNode &base, bo
 	return result;
 }
 
-Common::FSNode resolvePathWithFuzz(Common::String &path, Common::FSNode &base, bool directory) {
-	Common::FSNode result = resolvePath(path, base, directory);
-	if (!result.exists()) {
+Common::Path resolvePathWithFuzz(Common::String &path, Common::Path &base, bool directory) {
+	Common::Path result = resolvePath(path, base, directory);
+	if (result.empty()) {
 		// Try again with all non-FAT compatible characters stripped
 		Common::String newPath = stripMacPath(path.c_str());
 		if (newPath != path)
 			result = resolvePath(newPath, base, directory);
 	}
-	if (!result.exists()) {
+	if (result.empty()) {
 		// Try again with the path horribly disfigured to fit into 8.3 DOS filenames
 		Common::String newPath = convert83Path(path);
 		if (newPath != path)
@@ -647,43 +655,25 @@ Common::FSNode resolvePathWithFuzz(Common::String &path, Common::FSNode &base, b
 	return result;
 }
 
-Common::FSNode resolvePartialPathWithFuzz(Common::String &path, Common::FSNode &base, bool directory) {
-	Common::FSNode result = resolvePartialPath(path, base, directory);
-	if (!result.exists()) {
+Common::Path resolvePartialPathWithFuzz(Common::String &path, Common::Path &base, bool directory) {
+	Common::Path result = resolvePartialPath(path, base, directory);
+	if (result.empty()) {
 		// Try again with all non-FAT compatible characters stripped
 		Common::String newPath = stripMacPath(path.c_str());
-		result = resolvePartialPath(newPath, base, directory);
+		if (newPath != path)
+			result = resolvePartialPath(newPath, base, directory);
 	}
-	if (!result.exists()) {
+	if (result.empty()) {
 		// Try again with the path horribly disfigured to fit into 8.3 DOS filenames
 		Common::String newPath = convert83Path(path);
-		result = resolvePartialPath(newPath, base, directory);
-	}
-	return result;
-}
-
-Common::Path nodeToPath(Common::FSNode &node) {
-	Common::StringArray base = Common::Path(g_director->getGameDataDir()->getPath()).splitComponents();
-	Common::StringArray target = Common::Path(node.getPath()).splitComponents();
-
-	if (target.size() < base.size()) {
-		warning("nodeToPath(): target not a subset");
-		return Common::Path();
-	}
-	for (auto &it : base) {
-		if (it != target[0]) {
-			warning("nodeToPath(): expected component %s, found %s", it.c_str(), target[0].c_str());
-			return Common::Path();
-		}
-		target.remove_at(0);
+		if (newPath != path)
+			result = resolvePartialPath(newPath, base, directory);
 	}
-	Common::Path result = Common::Path::joinComponents(target);
-	debug(9, "nodeToPath(): %s -> %s", node.getPath().c_str(), result.toString().c_str());
 	return result;
 }
 
 Common::Path findPath(Common::String &path, bool currentFolder, bool searchPaths, bool directory) {
-	Common::FSNode result, base;
+	Common::Path result, base;
 	debugN(9, "%s", recIndent());
 	debug(9, "findPath(): beginning search for \"%s\"", path.c_str());
 	// For an absolute path, first check it relative to the filesystem
@@ -691,36 +681,36 @@ Common::Path findPath(Common::String &path, bool currentFolder, bool searchPaths
 		debugN(9, "%s", recIndent());
 		debug(9, "findPath(): searching absolute path");
 		result = resolvePathWithFuzz(path, base, directory);
-		if (result.exists()) {
+		if (!result.empty()) {
 			debugN(9, "%s", recIndent());
-			debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), nodeToPath(result).toString().c_str());
-			return nodeToPath(result);
+			debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), result.toString().c_str());
+			return result;
 		}
 	}
 
 	if (currentFolder) {
 		Common::String currentPath = g_director->getCurrentPath();
-		Common::FSNode current = resolvePath(currentPath, base, true);
+		Common::Path current = resolvePath(currentPath, base, true);
 		debugN(9, "%s", recIndent());
-		debug(9, "findPath(): searching current folder %s", current.getPath().c_str());
+		debug(9, "findPath(): searching current folder %s", current.toString().c_str());
 		base = current;
 		result = resolvePartialPathWithFuzz(path, base, directory);
-		if (result.exists()) {
+		if (!result.empty()) {
 			debugN(9, "%s", recIndent());
-			debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), nodeToPath(result).toString().c_str());
-			return nodeToPath(result);
+			debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), result.toString().c_str());
+			return result;
 		}
 	}
 
 	// Fall back to checking the game root path
 	debugN(9, "%s", recIndent());
 	debug(9, "findPath(): searching game root path");
-	base = Common::FSNode();
+	base = Common::Path();
 	result = resolvePartialPathWithFuzz(path, base, directory);
-	if (result.exists()) {
+	if (!result.empty()) {
 		debugN(9, "%s", recIndent());
-		debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), nodeToPath(result).toString().c_str());
-		return nodeToPath(result);
+		debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), result.toString().c_str());
+		return result;
 	}
 
 	// Check each of the search paths in sequence
@@ -736,14 +726,9 @@ Common::Path findPath(Common::String &path, bool currentFolder, bool searchPaths
 			searchPathList.push_back(it);
 		}
 		for (auto &searchIn : searchPathList) {
-			// Ensure there's a trailing directory separator
-			Common::String separator = Common::String::format("%c", g_director->_dirSeparator);
-			if (!searchIn.hasSuffix(separator)) {
-				searchIn += separator;
-			}
-			base = Common::FSNode();
+			base = Common::Path();
 			base = resolvePathWithFuzz(searchIn, base, true);
-			if (!base.exists()) {
+			if (base.empty()) {
 				debugN(9, "%s", recIndent());
 				debug(9, "findPath(): couldn't resolve search path folder %s, skipping", searchIn.c_str());
 				continue;
@@ -751,10 +736,10 @@ Common::Path findPath(Common::String &path, bool currentFolder, bool searchPaths
 			debugN(9, "%s", recIndent());
 			debug(9, "findPath(): searching search path folder %s", searchIn.c_str());
 			result = resolvePartialPathWithFuzz(path, base, directory);
-			if (result.exists()) {
+			if (!result.empty()) {
 				debugN(9, "%s", recIndent());
-				debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), nodeToPath(result).toString().c_str());
-				return nodeToPath(result);
+				debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), result.toString().c_str());
+				return result;
 			}
 		}
 	}
diff --git a/engines/director/util.h b/engines/director/util.h
index 043fd0f8b48..e2ea8292bb6 100644
--- a/engines/director/util.h
+++ b/engines/director/util.h
@@ -41,11 +41,10 @@ Common::String unixToMacPath(const Common::String &path);
 
 Common::String getPath(Common::String path, Common::String cwd);
 
-Common::FSNode resolvePath(Common::String &path, Common::FSNode &base, bool directory);
-Common::FSNode resolvePartialPath(Common::String &path, Common::FSNode &base, bool directory);
-Common::FSNode resolvePathWithFuzz(Common::String &path, Common::FSNode &base, bool directory);
-Common::FSNode resolvePartialPathWithFuzz(Common::String &path, Common::FSNode &base, bool directory);
-Common::Path nodeToPath(Common::FSNode &node);
+Common::Path resolvePath(Common::String &path, Common::Path &base, bool directory);
+Common::Path resolvePartialPath(Common::String &path, Common::Path &base, bool directory);
+Common::Path resolvePathWithFuzz(Common::String &path, Common::Path &base, bool directory);
+Common::Path resolvePartialPathWithFuzz(Common::String &path, Common::Path &base, bool directory);
 Common::Path findPath(Common::String &path, bool currentFolder = true, bool searchPaths = true, bool directory = false);
 Common::Path findMoviePath(Common::String &path, bool currentFolder = true, bool searchPaths = true);
 Common::Path findAudioPath(Common::String &path, bool currentFolder = true, bool searchPaths = true);


Commit: 0d43130eda50442a084a14c996af5d3e67a0f065
    https://github.com/scummvm/scummvm/commit/0d43130eda50442a084a14c996af5d3e67a0f065
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Fix libId and getHandler for multi-cast support

Changed paths:
    engines/director/movie.cpp


diff --git a/engines/director/movie.cpp b/engines/director/movie.cpp
index 33a05f4288c..11774459d2d 100644
--- a/engines/director/movie.cpp
+++ b/engines/director/movie.cpp
@@ -155,8 +155,9 @@ void Movie::loadCastLibMapping(Common::SeekableReadStreamEndian &stream) {
 		stream.readUint16();
 		uint16 itemCount = stream.readUint16();
 		stream.readUint16();
-		uint16 libId = stream.readUint16() - CAST_LIB_OFFSET;
-		debugC(5, kDebugLoading, "Movie::loadCastLibMapping: name: %s, path: %s, itemCount: %d, libId: %d", utf8ToPrintable(name).c_str(), utf8ToPrintable(path).c_str(), itemCount, libId);
+		uint16 libResourceId = stream.readUint16();
+		uint16 libId = i + 1;
+		debugC(5, kDebugLoading, "Movie::loadCastLibMapping: name: %s, path: %s, itemCount: %d, libResourceId: %d, libId: %d", utf8ToPrintable(name).c_str(), utf8ToPrintable(path).c_str(), itemCount, libResourceId, libId);
 		Archive *castArchive = _movieArchive;
 		bool isExternal = !path.empty();
 		if (isExternal) {
@@ -487,8 +488,10 @@ ScriptContext *Movie::getScriptContext(ScriptType type, CastMemberID id) {
 }
 
 Symbol Movie::getHandler(const Common::String &name) {
-	if (_cast->_lingoArchive->functionHandlers.contains(name))
-		return _cast->_lingoArchive->functionHandlers[name];
+	for (auto &it : _casts) {
+		if (it._value->_lingoArchive->functionHandlers.contains(name))
+			return it._value->_lingoArchive->functionHandlers[name];
+	}
 
 	if (_sharedCast && _sharedCast->_lingoArchive->functionHandlers.contains(name))
 		return _sharedCast->_lingoArchive->functionHandlers[name];


Commit: 1253fc9e9ebc36c9da57cfad9ec240b5f094c404
    https://github.com/scummvm/scummvm/commit/1253fc9e9ebc36c9da57cfad9ec240b5f094c404
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: LINGO: Fix bytecode crashes for D5

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


diff --git a/engines/director/lingo/lingo-bytecode.cpp b/engines/director/lingo/lingo-bytecode.cpp
index 5c6932eb1fe..c4e04920466 100644
--- a/engines/director/lingo/lingo-bytecode.cpp
+++ b/engines/director/lingo/lingo-bytecode.cpp
@@ -356,11 +356,15 @@ Datum Lingo::findVarV4(int varType, const Datum &id) {
 				warning("BUILDBOT: findVarV4: no call frame");
 				return res;
 			}
-			if (id.asInt() % 6 != 0) {
-				warning("BUILDBOT: findVarV4: invalid var ID %d for var type %d (not divisible by 6)", id.asInt(), varType);
+			int stride = 6;
+			if (g_director->getVersion() >= 500) {
+				stride = 8;
+			}
+			if (id.asInt() % stride != 0) {
+				warning("BUILDBOT: findVarV4: invalid var ID %d for var type %d (not divisible by %d)", id.asInt(), varType, stride);
 				return res;
 			}
-			int varIndex = id.asInt() / 6;
+			int varIndex = id.asInt() / stride;
 			Common::Array<Common::String> *varNames = (varType == 4)
 				? callstack.back()->sp.argNames
 				: callstack.back()->sp.varNames;
@@ -827,6 +831,11 @@ void LC::cb_v4theentitynamepush() {
 	id.u.s = nullptr;
 	id.type = VOID;
 
+	if (!g_lingo->_theEntities.contains(name)) {
+		warning("BUILDBOT: cb_v4theentitynamepush: missing the entity %s", name.c_str());
+		g_lingo->push(Datum());
+		return;
+	}
 	TheEntity *entity = g_lingo->_theEntities[name];
 
 	debugC(3, kDebugLingoExec, "cb_v4theentitynamepush: %s", name.c_str());


Commit: 5cc9a5b59374dc37d890966169e94652641b6e2b
    https://github.com/scummvm/scummvm/commit/5cc9a5b59374dc37d890966169e94652641b6e2b
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Fix funcs debugger command to list from all casts

Changed paths:
    engines/director/debugger.cpp


diff --git a/engines/director/debugger.cpp b/engines/director/debugger.cpp
index d2ddd26ed6a..6356e2a43fa 100644
--- a/engines/director/debugger.cpp
+++ b/engines/director/debugger.cpp
@@ -395,14 +395,16 @@ bool Debugger::cmdFuncs(int argc, const char **argv) {
 		debugPrintf("  [empty]\n");
 	}
 	debugPrintf("\n");
-	debugPrintf("Cast functions:\n");
-	Cast *cast = movie->getCast();
-	if (cast && cast->_lingoArchive) {
-		debugPrintf("%s", cast->_lingoArchive->formatFunctionList("  ").c_str());
-	} else {
-		debugPrintf("  [empty]\n");
+	for (auto it : *movie->getCasts()) {
+		debugPrintf("Cast %d functions:\n", it._key);
+		Cast *cast = it._value;
+		if (cast && cast->_lingoArchive) {
+			debugPrintf("%s", cast->_lingoArchive->formatFunctionList("  ").c_str());
+		} else {
+			debugPrintf("  [empty]\n");
+		}
+		debugPrintf("\n");
 	}
-	debugPrintf("\n");
 	debugPrintf("Shared cast functions:\n");
 	Cast *sharedCast = movie->getSharedCast();
 	if (sharedCast && sharedCast->_lingoArchive) {


Commit: 93bd0741d66c454eea1126739c91f1fb487669f3
    https://github.com/scummvm/scummvm/commit/93bd0741d66c454eea1126739c91f1fb487669f3
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Interrogate SearchMan for path matches

Changed paths:
    engines/director/resource.cpp
    engines/director/util.cpp
    engines/director/util.h


diff --git a/engines/director/resource.cpp b/engines/director/resource.cpp
index 9ee2026c4a7..4602a1cb8c9 100644
--- a/engines/director/resource.cpp
+++ b/engines/director/resource.cpp
@@ -697,14 +697,16 @@ bool ProjectorArchive::loadArchive(Common::SeekableReadStream *stream) {
 			size = SWAP_BYTES_32(size);
 		}
 
-		debugC(1, kDebugLoading, "Entry: %s offset %lX tag %s size %d", arr[i].c_str(), long(stream->pos() - 8), tag2str(tag), size);
+		Common::Path path = toSafePath(arr[i]);
+
+		debugC(1, kDebugLoading, "Entry: %s offset %lX tag %s size %d", path.toString().c_str(), long(stream->pos() - 8), tag2str(tag), size);
 
 		Entry entry;
 
 		// subtract 8 since we want to include tag and size as well
 		entry.offset = static_cast<uint32>(stream->pos() - 8);
 		entry.size = size + 8;
-		_files[arr[i]] = entry;
+		_files[path.toString()] = entry;
 
 		// Align size for the next seek.
 		size += (size % 2);
diff --git a/engines/director/util.cpp b/engines/director/util.cpp
index 00dfcc14dbf..c4333b8f594 100644
--- a/engines/director/util.cpp
+++ b/engines/director/util.cpp
@@ -456,13 +456,29 @@ bool isAbsolutePath(Common::String &path) {
 	return false;
 }
 
+Common::Path toSafePath(Common::String &path) {
+	// Encode a Director raw path as a platform-independent path.
+	// This needs special care, as Mac filenames allow using '/' in them!
+	// - Scrub the pathname to be relative with the correct dir separator
+	// - Split it into tokens
+	// - Encode each token with punycode_encodefilename
+	// - Join the tokens back together with the default dir separator
+	Common::StringTokenizer pathList(convertPath(path), Common::String(g_director->_dirSeparator));
+	Common::Path result;
+	while (!pathList.empty()) {
+		Common::String token = pathList.nextToken();
+		token = Common::punycode_encodefilename(token);
+		if (!result.empty())
+			result.appendInPlace(Common::String(g_director->_dirSeparator), g_director->_dirSeparator);
+		result.appendInPlace(token);
+	}
+	return result;
+}
+
 Common::String convertPath(Common::String &path) {
 	if (path.empty())
 		return path;
 
-	debugN(9, "%s", recIndent());
-	debug(9, "convertPath(%s)", path.c_str());
-
 	if (!path.contains(':') && !path.contains('\\') && !path.contains('@')) {
 		return path;
 	}
@@ -553,6 +569,9 @@ Common::String convert83Path(Common::String &path) {
 }
 
 Common::Path resolvePath(Common::String &path, Common::Path &base, bool directory) {
+	// Path is the raw input from Director. Scrub it to be a clean relative path.
+	path = convertPath(path);
+
 	// Absolute path to the game directory
 	Common::Path gamePath = Common::Path(g_director->getGameDataDir()->getPath());
 	// Absolute path to the game directory + the base search path
@@ -564,8 +583,6 @@ Common::Path resolvePath(Common::String &path, Common::Path &base, bool director
 	// FSNode for the current walk location in the filesystem
 	Common::FSNode filesystem(testPath);
 
-	// Path is the raw input from Director. Scrub it to be a clean relative path.
-	path = convertPath(path);
 	// Split this into a component list for iteration.
 	Common::StringTokenizer directory_list(path, Common::String(g_director->_dirSeparator));
 	// newPath is our final result; construct this based on successful filesystem tests
@@ -574,6 +591,7 @@ Common::Path resolvePath(Common::String &path, Common::Path &base, bool director
 		newPath.appendInPlace(Common::String(g_director->_dirSeparator), g_director->_dirSeparator);
 
 	Common::FSList fslist;
+	bool exists = false;
 	while (!directory_list.empty()) {
 		Common::String token = directory_list.nextToken();
 		Common::String decodedToken = punycode_decodefilename(token);
@@ -586,7 +604,7 @@ Common::Path resolvePath(Common::String &path, Common::Path &base, bool director
 		if (!hasChildren)
 			continue;
 
-		bool exists = false;
+		exists = false;
 		for (auto &i : fslist) {
 			// for each element in the path, choose the first FSNode
 			// with a case-insensitive matching name
@@ -606,14 +624,67 @@ Common::Path resolvePath(Common::String &path, Common::Path &base, bool director
 			}
 		}
 		if (!exists) {
+			break;
+		}
+	}
+
+	if (exists) {
+		debugN(9, "%s", recIndent());
+		debug(9, "resolvePath(): Found filesystem match for %s -> %s", path.c_str(), newPath.toString().c_str());
+		return newPath;
+	}
+
+	// No filesystem match, check caches
+	newPath = toSafePath(path);
+	if (!directory) {
+		// Check SearchMan
+		if (SearchMan.hasFile(newPath)) {
+			debugN(9, "%s", recIndent());
+			debug(9, "resolvePath(): Found SearchMan match for %s -> %s", path.c_str(), newPath.toString().c_str());
+			return newPath;
+		}
+		// Check MacResArchive
+		if (Common::MacResManager::exists(newPath)) {
 			debugN(9, "%s", recIndent());
-			debug(9, "resolvePath(): No match found for %s", path.c_str());
-			return Common::Path();
+			debug(9, "resolvePath(): Found MacResManager match for %s -> %s", path.c_str(), newPath.toString().c_str());
+			return newPath;
+		}
+	} else {
+		// Iterate through every SearchMan file to check for directory matches
+		Common::StringArray srcComponents = newPath.splitComponents();
+		Common::ArchiveMemberList list;
+		SearchMan.listMembers(list);
+		for (auto &it : list) {
+			Common::Path test(it->getName());
+			Common::Path testParent = test.getParent();
+			Common::StringArray destComponents = testParent.splitComponents();
+			if (destComponents[destComponents.size() - 1].empty()) {
+				destComponents.pop_back();
+				testParent = Common::Path::joinComponents(destComponents);
+			}
+			if (srcComponents.size() != destComponents.size()) {
+				continue;
+			}
+			bool match = true;
+			for (int i = 0; i < srcComponents.size(); i++) {
+				Common::String component = Common::punycode_decodefilename(destComponents[i]);
+				if (!component.equalsIgnoreCase(srcComponents[i])) {
+					match = false;
+					break;
+				}
+			}
+			if (match) {
+				debugN(9, "%s", recIndent());
+				debug(9, "resolvePath(): Found SearchMan match for %s -> %s", path.c_str(), testParent.toString().c_str());
+				return testParent;
+			}
+
 		}
 	}
+
 	debugN(9, "%s", recIndent());
-	debug(9, "resolvePath(): Found filesystem match for %s -> %s", path.c_str(), newPath.toString().c_str());
-	return newPath;
+	debug(9, "resolvePath(): No match found for %s", path.c_str());
+	return Common::Path();
 }
 
 Common::Path resolvePartialPath(Common::String &path, Common::Path &base, bool directory) {
diff --git a/engines/director/util.h b/engines/director/util.h
index e2ea8292bb6..16bb05016ad 100644
--- a/engines/director/util.h
+++ b/engines/director/util.h
@@ -35,6 +35,7 @@ char *numToCastNum(int num);
 
 bool isAbsolutePath(Common::String &path);
 
+Common::Path toSafePath(Common::String &path);
 Common::String convertPath(Common::String &path);
 
 Common::String unixToMacPath(const Common::String &path);


Commit: 8a6d7d6c95001b554809212b0f2876d0e9bbb242
    https://github.com/scummvm/scummvm/commit/8a6d7d6c95001b554809212b0f2876d0e9bbb242
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Move file extension checks to innermost loop

Fixes shared cast detection in Meet Mediaband.

Changed paths:
    engines/director/util.cpp
    engines/director/util.h
    engines/director/window.cpp


diff --git a/engines/director/util.cpp b/engines/director/util.cpp
index c4333b8f594..f380419783b 100644
--- a/engines/director/util.cpp
+++ b/engines/director/util.cpp
@@ -568,7 +568,7 @@ Common::String convert83Path(Common::String &path) {
 	return convPath;
 }
 
-Common::Path resolvePath(Common::String &path, Common::Path &base, bool directory) {
+Common::Path resolveFSPath(Common::String &path, Common::Path &base, bool directory) {
 	// Path is the raw input from Director. Scrub it to be a clean relative path.
 	path = convertPath(path);
 
@@ -630,12 +630,34 @@ Common::Path resolvePath(Common::String &path, Common::Path &base, bool director
 
 	if (exists) {
 		debugN(9, "%s", recIndent());
-		debug(9, "resolvePath(): Found filesystem match for %s -> %s", path.c_str(), newPath.toString().c_str());
+		debug(9, "resolveFSPath(): Found filesystem match for %s -> %s", path.c_str(), newPath.toString().c_str());
 		return newPath;
 	}
 
+	return Common::Path();
+}
+
+Common::Path resolvePath(Common::String &path, Common::Path &base, bool directory, const char **exts) {
+	Common::Path result = resolveFSPath(path, base, directory);
+	if (!result.empty()) {
+		return result;
+	} else if (result.empty() && !directory && exts) {
+		Common::String fileBase = path;
+		if (hasExtension(fileBase))
+			fileBase = fileBase.substr(0, fileBase.size() - 4);
+		for (int i = 0; exts[i]; i++) {
+			Common::String fileExt = fileBase + exts[i];
+			result = resolveFSPath(fileExt, base, directory);
+			if (!result.empty())
+				return result;
+		}
+	}
+
 	// No filesystem match, check caches
-	newPath = toSafePath(path);
+	Common::Path newPath = base;
+	if (!newPath.empty())
+		newPath.appendInPlace(Common::String(g_director->_dirSeparator), g_director->_dirSeparator);
+	newPath.appendInPlace(toSafePath(path));
 	if (!directory) {
 		// Check SearchMan
 		if (SearchMan.hasFile(newPath)) {
@@ -687,7 +709,7 @@ Common::Path resolvePath(Common::String &path, Common::Path &base, bool director
 	return Common::Path();
 }
 
-Common::Path resolvePartialPath(Common::String &path, Common::Path &base, bool directory) {
+Common::Path resolvePartialPath(Common::String &path, Common::Path &base, bool directory, const char **exts) {
 	path = convertPath(path);
 	Common::StringArray tokens = Common::StringTokenizer(path, Common::String(g_director->_dirSeparator)).split();
 
@@ -700,7 +722,7 @@ Common::Path resolvePartialPath(Common::String &path, Common::Path &base, bool d
 				subpath += g_director->_dirSeparator;
 			}
 		}
-		result = resolvePath(subpath, base, directory);
+		result = resolvePath(subpath, base, directory, exts);
 		if (!result.empty()) {
 			break;
 		}
@@ -709,41 +731,41 @@ Common::Path resolvePartialPath(Common::String &path, Common::Path &base, bool d
 	return result;
 }
 
-Common::Path resolvePathWithFuzz(Common::String &path, Common::Path &base, bool directory) {
-	Common::Path result = resolvePath(path, base, directory);
+Common::Path resolvePathWithFuzz(Common::String &path, Common::Path &base, bool directory, const char **exts) {
+	Common::Path result = resolvePath(path, base, directory, exts);
 	if (result.empty()) {
 		// Try again with all non-FAT compatible characters stripped
 		Common::String newPath = stripMacPath(path.c_str());
 		if (newPath != path)
-			result = resolvePath(newPath, base, directory);
+			result = resolvePath(newPath, base, directory, exts);
 	}
 	if (result.empty()) {
 		// Try again with the path horribly disfigured to fit into 8.3 DOS filenames
 		Common::String newPath = convert83Path(path);
 		if (newPath != path)
-			result = resolvePath(newPath, base, directory);
+			result = resolvePath(newPath, base, directory, exts);
 	}
 	return result;
 }
 
-Common::Path resolvePartialPathWithFuzz(Common::String &path, Common::Path &base, bool directory) {
-	Common::Path result = resolvePartialPath(path, base, directory);
+Common::Path resolvePartialPathWithFuzz(Common::String &path, Common::Path &base, bool directory, const char **exts) {
+	Common::Path result = resolvePartialPath(path, base, directory, exts);
 	if (result.empty()) {
 		// Try again with all non-FAT compatible characters stripped
 		Common::String newPath = stripMacPath(path.c_str());
 		if (newPath != path)
-			result = resolvePartialPath(newPath, base, directory);
+			result = resolvePartialPath(newPath, base, directory, exts);
 	}
 	if (result.empty()) {
 		// Try again with the path horribly disfigured to fit into 8.3 DOS filenames
 		Common::String newPath = convert83Path(path);
 		if (newPath != path)
-			result = resolvePartialPath(newPath, base, directory);
+			result = resolvePartialPath(newPath, base, directory, exts);
 	}
 	return result;
 }
 
-Common::Path findPath(Common::String &path, bool currentFolder, bool searchPaths, bool directory) {
+Common::Path findPath(Common::String &path, bool currentFolder, bool searchPaths, bool directory, const char **exts) {
 	Common::Path result, base;
 	debugN(9, "%s", recIndent());
 	debug(9, "findPath(): beginning search for \"%s\"", path.c_str());
@@ -751,7 +773,7 @@ Common::Path findPath(Common::String &path, bool currentFolder, bool searchPaths
 	if (isAbsolutePath(path)) {
 		debugN(9, "%s", recIndent());
 		debug(9, "findPath(): searching absolute path");
-		result = resolvePathWithFuzz(path, base, directory);
+		result = resolvePathWithFuzz(path, base, directory, exts);
 		if (!result.empty()) {
 			debugN(9, "%s", recIndent());
 			debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), result.toString().c_str());
@@ -761,11 +783,11 @@ Common::Path findPath(Common::String &path, bool currentFolder, bool searchPaths
 
 	if (currentFolder) {
 		Common::String currentPath = g_director->getCurrentPath();
-		Common::Path current = resolvePath(currentPath, base, true);
+		Common::Path current = resolvePath(currentPath, base, true, exts);
 		debugN(9, "%s", recIndent());
 		debug(9, "findPath(): searching current folder %s", current.toString().c_str());
 		base = current;
-		result = resolvePartialPathWithFuzz(path, base, directory);
+		result = resolvePartialPathWithFuzz(path, base, directory, exts);
 		if (!result.empty()) {
 			debugN(9, "%s", recIndent());
 			debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), result.toString().c_str());
@@ -777,7 +799,7 @@ Common::Path findPath(Common::String &path, bool currentFolder, bool searchPaths
 	debugN(9, "%s", recIndent());
 	debug(9, "findPath(): searching game root path");
 	base = Common::Path();
-	result = resolvePartialPathWithFuzz(path, base, directory);
+	result = resolvePartialPathWithFuzz(path, base, directory, exts);
 	if (!result.empty()) {
 		debugN(9, "%s", recIndent());
 		debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), result.toString().c_str());
@@ -798,7 +820,7 @@ Common::Path findPath(Common::String &path, bool currentFolder, bool searchPaths
 		}
 		for (auto &searchIn : searchPathList) {
 			base = Common::Path();
-			base = resolvePathWithFuzz(searchIn, base, true);
+			base = resolvePathWithFuzz(searchIn, base, true, exts);
 			if (base.empty()) {
 				debugN(9, "%s", recIndent());
 				debug(9, "findPath(): couldn't resolve search path folder %s, skipping", searchIn.c_str());
@@ -806,7 +828,7 @@ Common::Path findPath(Common::String &path, bool currentFolder, bool searchPaths
 			}
 			debugN(9, "%s", recIndent());
 			debug(9, "findPath(): searching search path folder %s", searchIn.c_str());
-			result = resolvePartialPathWithFuzz(path, base, directory);
+			result = resolvePartialPathWithFuzz(path, base, directory, exts);
 			if (!result.empty()) {
 				debugN(9, "%s", recIndent());
 				debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), result.toString().c_str());
@@ -821,10 +843,6 @@ Common::Path findPath(Common::String &path, bool currentFolder, bool searchPaths
 }
 
 Common::Path findMoviePath(Common::String &path, bool currentFolder, bool searchPaths) {
-	Common::Path result = findPath(path, currentFolder, searchPaths, false);
-	if (!result.empty())
-		return result;
-
 	const char *extsD3[] = { ".MMM", nullptr };
 	const char *extsD4[] = { ".DIR", ".DXR", ".EXE", nullptr };
 	const char *extsD5[] = { ".DIR", ".DXR", ".CST", ".CXT", ".EXE", nullptr };
@@ -841,38 +859,14 @@ Common::Path findMoviePath(Common::String &path, bool currentFolder, bool search
 		exts = extsD5;
 	}
 
-	Common::String fileBase = path;
-	if (hasExtension(fileBase))
-		fileBase = fileBase.substr(0, fileBase.size() - 4);
-
-	for (int i = 0; exts[i]; i++) {
-		Common::String newPath = fileBase + exts[i];
-
-		result = findPath(newPath, currentFolder, searchPaths, false);
-		if (!result.empty())
-			break;
-	}
+	Common::Path result = findPath(path, currentFolder, searchPaths, false, exts);
 	return result;
 }
 
 Common::Path findAudioPath(Common::String &path, bool currentFolder, bool searchPaths) {
-	Common::Path result = findPath(path, currentFolder, searchPaths, false);
-	if (!result.empty())
-		return result;
-
 	const char *exts[] = { ".AIF", ".WAV", nullptr };
 
-	Common::String fileBase = path;
-	if (hasExtension(fileBase))
-		fileBase = fileBase.substr(0, fileBase.size() - 4);
-
-	for (int i = 0; exts[i]; i++) {
-		Common::String newPath = fileBase + exts[i];
-
-		result = findPath(newPath, currentFolder, searchPaths, false);
-		if (!result.empty())
-			break;
-	}
+	Common::Path result = findPath(path, currentFolder, searchPaths, false, exts);
 	return result;
 }
 
diff --git a/engines/director/util.h b/engines/director/util.h
index 16bb05016ad..8ae168ff38b 100644
--- a/engines/director/util.h
+++ b/engines/director/util.h
@@ -42,11 +42,12 @@ Common::String unixToMacPath(const Common::String &path);
 
 Common::String getPath(Common::String path, Common::String cwd);
 
-Common::Path resolvePath(Common::String &path, Common::Path &base, bool directory);
-Common::Path resolvePartialPath(Common::String &path, Common::Path &base, bool directory);
-Common::Path resolvePathWithFuzz(Common::String &path, Common::Path &base, bool directory);
-Common::Path resolvePartialPathWithFuzz(Common::String &path, Common::Path &base, bool directory);
-Common::Path findPath(Common::String &path, bool currentFolder = true, bool searchPaths = true, bool directory = false);
+Common::Path resolveFSPath(Common::String &path, Common::Path &base, bool directory);
+Common::Path resolvePath(Common::String &path, Common::Path &base, bool directory, const char **exts);
+Common::Path resolvePartialPath(Common::String &path, Common::Path &base, bool directory, const char **exts);
+Common::Path resolvePathWithFuzz(Common::String &path, Common::Path &base, bool directory, const char **exts);
+Common::Path resolvePartialPathWithFuzz(Common::String &path, Common::Path &base, bool directory, const char **exts);
+Common::Path findPath(Common::String &path, bool currentFolder = true, bool searchPaths = true, bool directory = false, const char **exts = nullptr);
 Common::Path findMoviePath(Common::String &path, bool currentFolder = true, bool searchPaths = true);
 Common::Path findAudioPath(Common::String &path, bool currentFolder = true, bool searchPaths = true);
 
diff --git a/engines/director/window.cpp b/engines/director/window.cpp
index ee18011769d..720e69fa4e2 100644
--- a/engines/director/window.cpp
+++ b/engines/director/window.cpp
@@ -491,11 +491,9 @@ Common::Path Window::getSharedCastPath() {
 		}
 	} else if (_vm->getVersion() < 500) {
 		namesToTry.push_back("Shared.dir");
-		namesToTry.push_back("Shared.dxr");
 	} else {
 		// TODO: Does D5 actually support D4-style shared cast?
 		namesToTry.push_back("Shared.cst");
-		namesToTry.push_back("Shared.cxt");
 	}
 
 	Common::Path result;


Commit: 8555abd76a5c53d374bb152c8d10fff401fed1dd
    https://github.com/scummvm/scummvm/commit/8555abd76a5c53d374bb152c8d10fff401fed1dd
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: XOBJ: Add another alias for FlushXObj and FixPaletteXObj

Fixes Chop Suey for Mac.

Changed paths:
    engines/director/lingo/xlibs/flushxobj.cpp
    engines/director/lingo/xlibs/palxobj.cpp


diff --git a/engines/director/lingo/xlibs/flushxobj.cpp b/engines/director/lingo/xlibs/flushxobj.cpp
index c8031d8ee40..3b681431f16 100644
--- a/engines/director/lingo/xlibs/flushxobj.cpp
+++ b/engines/director/lingo/xlibs/flushxobj.cpp
@@ -70,6 +70,7 @@ const char *FlushXObj::fileNames[] = {
 	"FlushEvents",
 	"FlushXObj",
 	"Johnny",
+	"Johnny.XObj",
 	"Toilet",
 	nullptr
 };
diff --git a/engines/director/lingo/xlibs/palxobj.cpp b/engines/director/lingo/xlibs/palxobj.cpp
index 325cc2b2005..bfc961140e0 100644
--- a/engines/director/lingo/xlibs/palxobj.cpp
+++ b/engines/director/lingo/xlibs/palxobj.cpp
@@ -23,6 +23,7 @@
  *
  * USED IN:
  * Majestic-mac
+ * chopsuey-mac
  *
  *************************************/
 
@@ -57,6 +58,7 @@ const char *PalXObj::xlibName = "FixPalette";
 const char *PalXObj::fileNames[] = {
 	"PalXObj",
 	"FixPalette",
+	"FixPaletteXObj",
 	nullptr
 };
 


Commit: 70ffd87c62049c8c0326d61b1d25193ce6674c08
    https://github.com/scummvm/scummvm/commit/70ffd87c62049c8c0326d61b1d25193ce6674c08
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Add guardrails to loadSampleSounds

Changed paths:
    engines/director/cast.cpp
    engines/director/sound.cpp


diff --git a/engines/director/cast.cpp b/engines/director/cast.cpp
index c7567cdbf76..32ae1a9908b 100644
--- a/engines/director/cast.cpp
+++ b/engines/director/cast.cpp
@@ -812,8 +812,9 @@ void Cast::loadExternalSound(Common::SeekableReadStreamEndian &stream) {
 	Common::String str = stream.readString();
 	str.trim();
 	debugC(1, kDebugLoading, "****** Loading External Sound File %s", str.c_str());
+	str = g_director->getCurrentPath() + str;
 
-	Common::String resPath = g_director->getCurrentPath() + str;
+	Common::Path resPath = findPath(str, true, true, false);
 
 	g_director->openArchive(resPath);
 }
diff --git a/engines/director/sound.cpp b/engines/director/sound.cpp
index 7eba28030f7..601cd0fa0d0 100644
--- a/engines/director/sound.cpp
+++ b/engines/director/sound.cpp
@@ -368,6 +368,10 @@ void DirectorSound::loadSampleSounds(uint type) {
 	Archive *archive = nullptr;
 
 	for (auto &it : g_director->_allOpenResFiles) {
+		if (!g_director->_allSeenResFiles.contains(it)) {
+			warning("DirectorSound::loadSampleSounds(): file %s not found in allSeenResFiles, skipping", it.toString().c_str());
+			break;
+		}
 		Common::Array<uint16> idList = g_director->_allSeenResFiles[it]->getResourceIDList(tag);
 		for (uint j = 0; j < idList.size(); j++) {
 			if ((idList[j] & 0xFF) == type) {
@@ -378,6 +382,11 @@ void DirectorSound::loadSampleSounds(uint type) {
 		}
 	}
 
+	if (!archive) {
+		warning("DirectorSound::loadSampleSounds(): could not find a valid archive");
+		return;
+	}
+
 	if (id == 0xFF) {
 		warning("Score::loadSampleSounds: can not find CSND resource with id %d", type);
 		return;


Commit: aa32a51c47bcc51b6f7fd8c75eaefb3a16601033
    https://github.com/scummvm/scummvm/commit/aa32a51c47bcc51b6f7fd8c75eaefb3a16601033
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: More fixes for partial filename matching

For Macintosh, it is possible to have filenames with a "/" in them.
Dumper Companion will punyencode this and any other non-ASCII characters
when extracting from a Macintosh filesystem. As such, when checking
components of a filename, we should attempt to match against the punydecoded
name, but always return the "real" name (i.e. the name of the file in
the local filesystem, possibly punyencoded).

In addition, it is possible for a movie A/B/C.DIR to go to a movie
"E/F.DIR", where the actual path is A/E/F.DIR. To make the partial matching
work, first we try removing each component from the start of the target
path, then retry all combinations again after removing a component from
the end of the source directory, etc.

Fixes several movie switches in The Seven Colors.

Changed paths:
    common/fs.h
    engines/director/util.cpp


diff --git a/common/fs.h b/common/fs.h
index 9101bf98a3b..a8faff095f0 100644
--- a/common/fs.h
+++ b/common/fs.h
@@ -79,7 +79,6 @@ private:
 	 */
 	FSNode(AbstractFSNode *realNode);
 
-	String getRealName() const;
 public:
 	/**
 	 * Flag to tell listDir() which kind of files to list.
@@ -169,6 +168,16 @@ public:
 	 */
 	virtual String getName() const;
 
+	/**
+	 * Return a string representation of the name of the file, without any
+	 * Punycode transformation. This can be used e.g. by detection code that
+	 * relies on matching the name of a given file. However, it is *not*
+	 * suitable for use with fopen / File::open, nor should it be archived.
+	 *
+	 * @return The file name.
+	 */
+	virtual String getRealName() const;
+
 	/**
 	 * Return a string representation of the file that is suitable for
 	 * archiving (i.e. writing to the config file). This will usually be a
diff --git a/engines/director/util.cpp b/engines/director/util.cpp
index f380419783b..34820b7631a 100644
--- a/engines/director/util.cpp
+++ b/engines/director/util.cpp
@@ -593,8 +593,7 @@ Common::Path resolveFSPath(Common::String &path, Common::Path &base, bool direct
 	Common::FSList fslist;
 	bool exists = false;
 	while (!directory_list.empty()) {
-		Common::String token = directory_list.nextToken();
-		Common::String decodedToken = punycode_decodefilename(token);
+		Common::String token = punycode_decodefilename(directory_list.nextToken());
 		fslist.clear();
 		Common::FSNode::ListMode mode = Common::FSNode::kListDirectoriesOnly;
 		if (directory_list.empty() && !directory) {
@@ -608,14 +607,15 @@ Common::Path resolveFSPath(Common::String &path, Common::Path &base, bool direct
 		for (auto &i : fslist) {
 			// for each element in the path, choose the first FSNode
 			// with a case-insensitive matching name
-			if (i.getName().equalsIgnoreCase(decodedToken)) {
+			Common::String decodedName = i.getName();
+			if (decodedName.equalsIgnoreCase(token)) {
 				// If this the final path component, check if we're allowed to match with a directory
 				if (directory_list.empty() && (directory != i.isDirectory())) {
 					continue;
 				}
 
 				exists = true;
-				newPath.appendInPlace(i.getName());
+				newPath.appendInPlace(i.getRealName());
 				if (!directory_list.empty() && !newPath.empty())
 					newPath.appendInPlace(Common::String(g_director->_dirSeparator), g_director->_dirSeparator);
 
@@ -688,7 +688,7 @@ Common::Path resolvePath(Common::String &path, Common::Path &base, bool director
 				continue;
 			}
 			bool match = true;
-			for (int i = 0; i < srcComponents.size(); i++) {
+			for (size_t i = 0; i < srcComponents.size(); i++) {
 				Common::String component = Common::punycode_decodefilename(destComponents[i]);
 				if (!component.equalsIgnoreCase(srcComponents[i])) {
 					match = false;
@@ -711,22 +711,37 @@ Common::Path resolvePath(Common::String &path, Common::Path &base, bool director
 
 Common::Path resolvePartialPath(Common::String &path, Common::Path &base, bool directory, const char **exts) {
 	path = convertPath(path);
-	Common::StringArray tokens = Common::StringTokenizer(path, Common::String(g_director->_dirSeparator)).split();
-
 	Common::Path result;
-	while (tokens.size()) {
-		Common::String subpath;
-		for (uint i = 0; i < tokens.size(); i++) {
-			subpath += tokens[i];
-			if (i < tokens.size() - 1) {
-				subpath += g_director->_dirSeparator;
+
+	Common::StringArray baseTokens = base.splitComponents();
+	bool basesLeft = true;
+	while (basesLeft) {
+		Common::Path testBase = Common::Path::joinComponents(baseTokens);
+
+		// Try removing leading components of the target path
+		Common::StringArray tokens = Common::StringTokenizer(path, Common::String(g_director->_dirSeparator)).split();
+
+		while (tokens.size()) {
+			Common::String subpath;
+			for (uint i = 0; i < tokens.size(); i++) {
+				subpath += tokens[i];
+				if (i < tokens.size() - 1) {
+					subpath += g_director->_dirSeparator;
+				}
 			}
+			result = resolvePath(subpath, testBase, directory, exts);
+			if (!result.empty()) {
+				break;
+			}
+			tokens.remove_at(0);
 		}
-		result = resolvePath(subpath, base, directory, exts);
-		if (!result.empty()) {
+		if (!result.empty())
 			break;
+		if (!baseTokens.size()) {
+			basesLeft = false;
+		} else {
+			baseTokens.pop_back();
 		}
-		tokens.remove_at(0);
 	}
 	return result;
 }


Commit: 36cb4594c205806bf7c44c9705af5464d094a56f
    https://github.com/scummvm/scummvm/commit/36cb4594c205806bf7c44c9705af5464d094a56f
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Fix ProjectorArchive path resolution

Changed paths:
    engines/director/archive.h
    engines/director/resource.cpp


diff --git a/engines/director/archive.h b/engines/director/archive.h
index e1622d8a9d0..2774ac50fc1 100644
--- a/engines/director/archive.h
+++ b/engines/director/archive.h
@@ -161,7 +161,7 @@ protected:
 
 class ProjectorArchive : public Common::Archive {
 public:
-	ProjectorArchive(Common::String path);
+	ProjectorArchive(Common::Path path);
 	~ProjectorArchive() override;
 
 	bool hasFile(const Common::Path &path) const override;
@@ -179,7 +179,7 @@ private:
 	};
 	typedef Common::HashMap<Common::String, Entry, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> FileMap;
 	FileMap _files;
-	Common::String _path;
+	Common::Path _path;
 
 	bool _isLoaded;
 };
diff --git a/engines/director/resource.cpp b/engines/director/resource.cpp
index 4602a1cb8c9..5489815544a 100644
--- a/engines/director/resource.cpp
+++ b/engines/director/resource.cpp
@@ -70,7 +70,8 @@ Common::Error Window::loadInitialMovie() {
 	probeResources(_mainArchive);
 
 	// Load multiple-resources based executable file (Projector)
-	ProjectorArchive *multiArchive = new ProjectorArchive(_vm->getRawEXEName());
+	Common::String rawEXE = _vm->getRawEXEName();
+	ProjectorArchive *multiArchive = new ProjectorArchive(findPath(rawEXE));
 	if (multiArchive->isLoaded()) {
 		// A valid projector archive, add to SearchMan
 		SearchMan.add(_vm->getRawEXEName(), multiArchive);
@@ -552,7 +553,7 @@ void Window::loadStartMovieXLibs() {
 	g_lingo->openXLib("SerialPort", kXObj);
 }
 
-ProjectorArchive::ProjectorArchive(Common::String path)
+ProjectorArchive::ProjectorArchive(Common::Path path)
 	: _path(path), _files() {
 
 	// Buffer 100K into memory
@@ -571,10 +572,9 @@ ProjectorArchive::ProjectorArchive(Common::String path)
 Common::SeekableReadStream *ProjectorArchive::createBufferedReadStream() {
 	const uint32 READ_BUFFER_SIZE = 1024 * 100;
 
-	Common::Path path(_path, g_director->_dirSeparator);
-	Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(path);
+	Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(_path);
 	if (!stream) {
-		warning("ProjectorArchive::createBufferedReadStream(): Cannot open %s", path.toString().c_str());
+		warning("ProjectorArchive::createBufferedReadStream(): Cannot open %s", _path.toString().c_str());
 		return nullptr;
 	}
 
@@ -611,7 +611,7 @@ bool ProjectorArchive::loadArchive(Common::SeekableReadStream *stream) {
 	stream->seek(rifxOffset);
 	tag = stream->readUint32BE();
 
-	debugC(1, kDebugLoading, "File: %s off: 0x%x, tag: %s rifx: 0x%x", _path.c_str(), off, tag2str(tag), rifxOffset);
+	debugC(1, kDebugLoading, "File: %s off: 0x%x, tag: %s rifx: 0x%x", _path.toString().c_str(), off, tag2str(tag), rifxOffset);
 
 	// Try to locate the very next Dict tag(byte-by-byte)
 	tag = stream->readUint32BE();


Commit: 63c791aeda61eca89cb9e47c517e645a0b5e713f
    https://github.com/scummvm/scummvm/commit/63c791aeda61eca89cb9e47c517e645a0b5e713f
Author: Scott Percival (code at moral.net.au)
Date: 2023-07-08T22:01:00+03:00

Commit Message:
DIRECTOR: Don't skip frame delay when switching frames

Fixes animation playback speed in The Seven Colors.

Changed paths:
    engines/director/score.cpp


diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index 318c80cb0a4..22b52a65254 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -345,7 +345,7 @@ void Score::update() {
 			} else {
 				_waitForVideoChannel = 0;
 			}
-		} else if (g_system->getMillis() < _nextFrameTime && !_nextFrame) {
+		} else if (g_system->getMillis() < _nextFrameTime) {
 			keepWaiting = true;
 		}
 
@@ -453,7 +453,7 @@ void Score::update() {
 			if (g_director->_fpsLimit)
 				_currentFrameRate = MIN(g_director->_fpsLimit, _currentFrameRate);
 			_nextFrameTime = g_system->getMillis() + 1000.0 / (float)_currentFrameRate;
-			debugC(5, kDebugLoading, "Score::update(): setting _nextFrameTime to %d based on a framerate of %d", _nextFrameTime, tempo);
+			debugC(5, kDebugLoading, "Score::update(): setting _nextFrameTime to %d based on a framerate of %d", _nextFrameTime, _currentFrameRate);
 		} else {
 			if (tempo == 128) {
 				_waitForClick = true;




More information about the Scummvm-git-logs mailing list