[Scummvm-git-logs] scummvm master -> 88271582501f1f2b3c53113a07a0ee5ef74da444

elasota noreply at scummvm.org
Tue Jun 21 05:38:25 UTC 2022


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

Summary:
8827158250 MTROPOLIS: Move boot code out and make it more generic


Commit: 88271582501f1f2b3c53113a07a0ee5ef74da444
    https://github.com/scummvm/scummvm/commit/88271582501f1f2b3c53113a07a0ee5ef74da444
Author: elasota (ejlasota at gmail.com)
Date: 2022-06-21T01:37:00-04:00

Commit Message:
MTROPOLIS: Move boot code out and make it more generic

Changed paths:
  A engines/mtropolis/boot.cpp
  A engines/mtropolis/boot.h
    engines/mtropolis/module.mk
    engines/mtropolis/mtropolis.cpp
    engines/mtropolis/runtime.cpp
    engines/mtropolis/runtime.h


diff --git a/engines/mtropolis/boot.cpp b/engines/mtropolis/boot.cpp
new file mode 100644
index 00000000000..1a482487d6b
--- /dev/null
+++ b/engines/mtropolis/boot.cpp
@@ -0,0 +1,868 @@
+/* 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/crc.h"
+#include "common/file.h"
+#include "common/macresman.h"
+#include "common/stuffit.h"
+#include "common/winexe.h"
+
+#include "graphics/maccursor.h"
+#include "graphics/wincursor.h"
+
+#include "mtropolis/boot.h"
+#include "mtropolis/detection.h"
+#include "mtropolis/runtime.h"
+
+#include "mtropolis/plugin/obsidian.h"
+#include "mtropolis/plugin/standard.h"
+#include "mtropolis/plugins.h"
+
+namespace MTropolis {
+
+namespace Boot {
+
+enum FileCategory {
+	kFileCategoryPlayer,
+	kFileCategoryExtension,
+	kFileCategoryProjectAdditionalSegment,
+	kFileCategoryProjectMainSegment,
+
+	kFileCategorySpecial,
+
+	kFileCategoryUnknown,
+};
+
+struct FileIdentification {
+	Common::String fileName;
+	FileCategory category;
+
+	uint32 macType;
+	uint32 macCreator;
+	Common::SharedPtr<Common::MacResManager> resMan;
+	Common::SharedPtr<Common::SeekableReadStream> stream;
+};
+
+static void initResManForFile(FileIdentification &f) {
+	if (!f.resMan) {
+		f.resMan.reset(new Common::MacResManager());
+		if (!f.resMan->open(f.fileName))
+			error("Failed to open resources of file '%s'", f.fileName.c_str());
+	}
+}
+
+class GameDataHandler {
+public:
+	virtual ~GameDataHandler();
+
+	virtual void unpackAdditionalFiles(Common::Array<Common::SharedPtr<ProjectPersistentResource> > &persistentResources, Common::Array<FileIdentification> &files);
+	virtual void categorizeSpecialFiles(Common::Array<FileIdentification> &files);
+	virtual void addPlugIns(ProjectDescription &projectDesc, const Common::Array<FileIdentification> &files);
+};
+
+GameDataHandler::~GameDataHandler() {
+}
+
+void GameDataHandler::unpackAdditionalFiles(Common::Array<Common::SharedPtr<ProjectPersistentResource> > &persistentResources, Common::Array<FileIdentification> &files) {
+}
+
+void GameDataHandler::categorizeSpecialFiles(Common::Array<FileIdentification> &files) {
+}
+
+void GameDataHandler::addPlugIns(ProjectDescription &projectDesc, const Common::Array<FileIdentification> &files) {
+	Common::SharedPtr<MTropolis::PlugIn> standardPlugIn = PlugIns::createStandard();
+	projectDesc.addPlugIn(standardPlugIn);
+}
+
+template<class T>
+class PersistentResource : public ProjectPersistentResource {
+public:
+	explicit PersistentResource(const Common::SharedPtr<T> &item);
+	const Common::SharedPtr<T> &getItem();
+
+	static Common::SharedPtr<ProjectPersistentResource> wrap(const Common::SharedPtr<T> &item);
+
+private:
+	Common::SharedPtr<T> _item;
+};
+
+template<class T>
+PersistentResource<T>::PersistentResource(const Common::SharedPtr<T> &item) : _item(item) {
+}
+
+template<class T>
+const Common::SharedPtr<T> &PersistentResource<T>::getItem() {
+	return _item;
+}
+
+template<class T>
+Common::SharedPtr<ProjectPersistentResource> PersistentResource<T>::wrap(const Common::SharedPtr<T> &item) {
+	return Common::SharedPtr<ProjectPersistentResource>(new PersistentResource<T>(item));
+}
+
+class ObsidianGameDataHandler : public GameDataHandler {
+public:
+	explicit ObsidianGameDataHandler(const MTropolisGameDescription &gameDesc);
+
+	void unpackAdditionalFiles(Common::Array<Common::SharedPtr<ProjectPersistentResource> > &persistentResources, Common::Array<FileIdentification> &files) override;
+	void categorizeSpecialFiles(Common::Array<FileIdentification> &files) override;
+	void addPlugIns(ProjectDescription &projectDesc, const Common::Array<FileIdentification> &files) override;
+
+private:
+	bool _isMac;
+	bool _isRetail;
+	bool _isEnglish;
+
+	void unpackMacRetailInstaller(Common::Array<Common::SharedPtr<ProjectPersistentResource> > &persistentResources, Common::Array<FileIdentification> &files);
+	Common::SharedPtr<Obsidian::WordGameData> loadWinWordGameData();
+	Common::SharedPtr<Obsidian::WordGameData> loadMacWordGameData();
+
+	Common::SharedPtr<Common::Archive> _installerArchive;
+};
+
+ObsidianGameDataHandler::ObsidianGameDataHandler(const MTropolisGameDescription &gameDesc) {
+	_isMac = (gameDesc.desc.platform == Common::kPlatformMacintosh);
+	_isEnglish = (gameDesc.desc.language == Common::EN_ANY);
+	_isRetail = ((gameDesc.desc.flags & ADGF_DEMO) == 0);
+}
+
+struct MacInstallerUnpackRequest {
+	const char *fileName;
+	uint32 type;
+	uint32 creator;
+};
+
+void ObsidianGameDataHandler::unpackAdditionalFiles(Common::Array<Common::SharedPtr<ProjectPersistentResource> > &persistentResources, Common::Array<FileIdentification> &files) {
+	if (_isMac && _isRetail)
+		unpackMacRetailInstaller(persistentResources, files);
+}
+
+void ObsidianGameDataHandler::unpackMacRetailInstaller(Common::Array<Common::SharedPtr<ProjectPersistentResource> > &persistentResources, Common::Array<FileIdentification> &files) {
+	const MacInstallerUnpackRequest requests[] = {
+		{"Obsidian", MKTAG('A', 'P', 'P', 'L'), MKTAG('M', 'f', 'P', 'l')},
+		{"Basic.rPP", MKTAG('M', 'F', 'X', 'O'), MKTAG('M', 'f', 'P', 'l')},
+		{"mCursors.cPP", MKTAG('M', 'F', 'c', 'r'), MKTAG('M', 'f', 'P', 'l')},
+		{"Obsidian.cPP", MKTAG('M', 'F', 'c', 'r'), MKTAG('M', 'f', 'M', 'f')},
+		{"RSGKit.rPP", MKTAG('M', 'F', 'c', 'o'), MKTAG('M', 'f', 'M', 'f')},
+	};
+
+	Common::SharedPtr<Common::MacResManager> installerResMan(new Common::MacResManager());
+	persistentResources.push_back(PersistentResource<Common::MacResManager>::wrap(installerResMan));
+
+	if (!installerResMan->open("Obsidian Installer"))
+		error("Failed to open Obsidian Installer");
+
+	if (!installerResMan->hasDataFork())
+		error("Obsidian Installer has no data fork");
+
+	Common::SeekableReadStream *installerDataForkStream = installerResMan->getDataFork();
+
+	// Not counted/persisted because the StuffIt archive owns the stream
+	_installerArchive.reset(Common::createStuffItArchive(installerDataForkStream));
+	persistentResources.push_back(PersistentResource<Common::Archive>::wrap(_installerArchive));
+
+	if (!_installerArchive) {
+		delete installerDataForkStream;
+		installerDataForkStream = nullptr;
+
+		error("Failed to open Obsidian Installer archive");
+	}
+
+	debug(1, "Unpacking resource files...");
+
+	for (const MacInstallerUnpackRequest &request : requests) {
+		Common::SharedPtr<Common::MacResManager> resMan(new Common::MacResManager());
+
+		if (!resMan->open(request.fileName, *_installerArchive))
+			error("Failed to open file '%s' from installer package", request.fileName);
+
+		FileIdentification ident;
+		ident.fileName = request.fileName;
+		ident.macCreator = request.creator;
+		ident.macType = request.type;
+		ident.resMan = resMan;
+		ident.category = kFileCategoryUnknown;
+		files.push_back(ident);
+	}
+
+	{
+		debug(1, "Unpacking startup segment...");
+
+		Common::SharedPtr<Common::SeekableReadStream> startupStream(_installerArchive->createReadStreamForMember("Obsidian Data 1"));
+
+		FileIdentification ident;
+		ident.fileName = "Obsidian Data 1";
+		ident.macCreator = MKTAG('M', 'f', 'P', 'l');
+		ident.macType = MKTAG('M', 'F', 'm', 'm');
+		ident.category = kFileCategoryUnknown;
+		ident.stream = startupStream;
+		files.push_back(ident);
+	}
+}
+
+void ObsidianGameDataHandler::categorizeSpecialFiles(Common::Array<FileIdentification> &files) {
+	// Flag the installer as Special so it doesn't get detected as the player due to being an application
+	// Flag RSGKit as Special so it doesn't get fed to the cursor loader
+	for (FileIdentification &file : files) {
+		if (file.fileName == "Obsidian Installer" || file.fileName == "RSGKit.rPP" || file.fileName == "RSGKit.r95")
+			file.category = kFileCategorySpecial;
+	}
+}
+
+void ObsidianGameDataHandler::addPlugIns(ProjectDescription &projectDesc, const Common::Array<FileIdentification> &files) {
+	Common::SharedPtr<Obsidian::WordGameData> wgData;
+
+	if (_isRetail && _isEnglish) {
+		if (_isMac)
+			wgData = loadMacWordGameData();
+		else
+			wgData = loadWinWordGameData();
+	}
+
+	Common::SharedPtr<Obsidian::ObsidianPlugIn> obsidianPlugIn(new Obsidian::ObsidianPlugIn(wgData));
+	projectDesc.addPlugIn(obsidianPlugIn);
+
+	Common::SharedPtr<MTropolis::PlugIn> standardPlugIn = PlugIns::createStandard();
+	static_cast<Standard::StandardPlugIn *>(standardPlugIn.get())->getHacks().allowGarbledListModData = true;
+	projectDesc.addPlugIn(standardPlugIn);
+}
+
+Common::SharedPtr<Obsidian::WordGameData> ObsidianGameDataHandler::loadMacWordGameData() {
+	Common::ArchiveMemberPtr rsgKit = _installerArchive->getMember(Common::Path("RSGKit.rPP"));
+	if (!rsgKit)
+		error("Couldn't find word game file in installer archive");
+
+	Common::SharedPtr<Obsidian::WordGameData> wgData(new Obsidian::WordGameData());
+
+	Common::SharedPtr<Common::SeekableReadStream> stream(rsgKit->createReadStream());
+	if (!stream)
+		error("Failed to open word game file");
+
+	Obsidian::WordGameLoadBucket buckets[] = {
+		{0, 0},             // 0 letters
+		{0xD7C8, 0xD7CC},   // 1 letter
+		{0xD7CC, 0xD84D},   // 2 letter
+		{0xD84D, 0xE25D},   // 3 letter
+		{0x1008C, 0x12AA8}, // 4 letter
+		{0x14C58, 0x19614}, // 5 letter
+		{0x1C73C, 0x230C1}, // 6 letter
+		{0x26D10, 0x2EB98}, // 7 letter
+		{0x32ADC, 0x3AA0E}, // 8 letter
+		{0x3E298, 0x45B88}, // 9 letter
+		{0x48BE8, 0x4E0D0}, // 10 letter
+		{0x4FFB0, 0x53460}, // 11 letter
+		{0x545F0, 0x56434}, // 12 letter
+		{0x56D84, 0x57CF0}, // 13 letter
+		{0x58158, 0x58833}, // 14 letter
+		{0x58A08, 0x58CD8}, // 15 letter
+		{0x58D8C, 0x58EAD}, // 16 letter
+		{0x58EF4, 0x58F72}, // 17 letter
+		{0x58F90, 0x58FDC}, // 18 letter
+		{0, 0},             // 19 letter
+		{0x58FEC, 0x59001}, // 20 letter
+		{0x59008, 0x59034}, // 21 letter
+		{0x5903C, 0x59053}, // 22 letter
+	};
+
+	if (!wgData->load(stream.get(), buckets, 23, 1, false))
+		error("Failed to load word game data");
+
+	return wgData;
+}
+
+Common::SharedPtr<Obsidian::WordGameData> ObsidianGameDataHandler::loadWinWordGameData() {
+	Common::File f;
+	if (!f.open("RSGKit.r95")) {
+		error("Couldn't open word game data file");
+		return nullptr;
+	}
+
+	Common::SharedPtr<Obsidian::WordGameData> wgData(new Obsidian::WordGameData());
+
+	Obsidian::WordGameLoadBucket buckets[] = {
+		{0, 0},             // 0 letters
+		{0x63D54, 0x63D5C}, // 1 letter
+		{0x63BF8, 0x63CA4}, // 2 letter
+		{0x627D8, 0x631E8}, // 3 letter
+		{0x5C2C8, 0x60628}, // 4 letter
+		{0x52F4C, 0x5919C}, // 5 letter
+		{0x47A64, 0x4F2FC}, // 6 letter
+		{0x3BC98, 0x43B20}, // 7 letter
+		{0x2DA78, 0x38410}, // 8 letter
+		{0x218F8, 0x2AA18}, // 9 letter
+		{0x19D78, 0x1FA18}, // 10 letter
+		{0x15738, 0x18BE8}, // 11 letter
+		{0x128A8, 0x14DE8}, // 12 letter
+		{0x1129C, 0x1243C}, // 13 letter
+		{0x10974, 0x110C4}, // 14 letter
+		{0x105EC, 0x108BC}, // 15 letter
+		{0x10454, 0x105A8}, // 16 letter
+		{0x103A8, 0x10434}, // 17 letter
+		{0x10348, 0x10398}, // 18 letter
+		{0, 0},             // 19 letter
+		{0x10328, 0x10340}, // 20 letter
+		{0x102EC, 0x1031C}, // 21 letter
+		{0x102D0, 0x102E8}, // 22 letter
+	};
+
+	if (!wgData->load(&f, buckets, 23, 4, true)) {
+		error("Failed to load word game data file");
+		return nullptr;
+	}
+
+	return wgData;
+}
+
+static bool getMacTypesForMacBinary(const char *fileName, uint32 &outType, uint32 &outCreator) {
+	Common::SharedPtr<Common::SeekableReadStream> stream(SearchMan.createReadStreamForMember(fileName));
+
+	if (!stream)
+		return false;
+
+	byte mbHeader[MBI_INFOHDR];
+	if (stream->read(mbHeader, MBI_INFOHDR) != MBI_INFOHDR)
+		return false;
+
+	if (mbHeader[0] != 0 || mbHeader[74] != 0)
+		return false;
+
+	Common::CRC_BINHEX crc;
+	crc.init();
+	uint16 checkSum = crc.crcFast(mbHeader, 124);
+
+	if (checkSum != READ_BE_UINT16(&mbHeader[124]))
+		return false;
+
+	outType = MKTAG(mbHeader[65], mbHeader[66], mbHeader[67], mbHeader[68]);
+	outCreator = MKTAG(mbHeader[69], mbHeader[70], mbHeader[71], mbHeader[72]);
+
+	return true;
+}
+
+static uint32 getWinFileEndingPseudoTag(const Common::String &fileName) {
+	byte bytes[4] = {0, 0, 0, 0};
+	size_t numInserts = 4;
+	if (fileName.size() < 4)
+		numInserts = fileName.size();
+
+	for (size_t i = 0; i < numInserts; i++)
+		bytes[i] = static_cast<byte>(invariantToLower(fileName[fileName.size() - numInserts + i]));
+
+	return MKTAG(bytes[0], bytes[1], bytes[2], bytes[3]);
+}
+
+static bool getMacTypesForFile(const char *fileName, uint32 &outType, uint32 &outCreator) {
+	if (getMacTypesForMacBinary(fileName, outType, outCreator))
+		return true;
+
+	return false;
+}
+
+static bool fileSortCompare(const FileIdentification &a, const FileIdentification &b) {
+	// If file names are mismatched then we want the first one to be shorter
+	if (a.fileName.size() > b.fileName.size())
+		return !fileSortCompare(b, a);
+
+	size_t aSize = a.fileName.size();
+	for (size_t i = 0; i < aSize; i++) {
+		char ac = invariantToLower(a.fileName[i]);
+		char bc = invariantToLower(b.fileName[i]);
+
+		if (ac < bc)
+			return true;
+		if (bc < ac)
+			return false;
+	}
+
+	return aSize < b.fileName.size();
+}
+
+static int resolveFileSegmentID(const Common::String &fileName) {
+	size_t lengthWithoutExtension = fileName.size();
+
+	size_t dotPos = fileName.findLastOf('.');
+	if (dotPos != Common::String::npos)
+		lengthWithoutExtension = dotPos;
+
+	int numDigits = 0;
+	int segmentID = 0;
+	int multiplier = 1;
+
+	for (size_t i = 0; i < lengthWithoutExtension; i++) {
+		size_t charPos = lengthWithoutExtension - 1 - i;
+		char c = fileName[charPos];
+
+		if (c >= '0' && c <= '9') {
+			int digit = c - '0';
+			segmentID += digit * multiplier;
+			multiplier *= 10;
+			numDigits++;
+		} else {
+			break;
+		}
+	}
+
+	if (numDigits == 0)
+		error("Unusual segment naming scheme");
+
+	return segmentID;
+}
+
+static void loadCursorsMac(FileIdentification &f, CursorGraphicCollection &cursorGraphics) {
+	initResManForFile(f);
+
+	const uint32 bwType = MKTAG('C', 'U', 'R', 'S');
+	const uint32 colorType = MKTAG('c', 'r', 's', 'r');
+
+	Common::MacResIDArray bwIDs = f.resMan->getResIDArray(bwType);
+	Common::MacResIDArray colorIDs = f.resMan->getResIDArray(colorType);
+
+	Common::MacResIDArray bwOnlyIDs;
+	for (Common::MacResIDArray::const_iterator bwIt = bwIDs.begin(), bwItEnd = bwIDs.end(); bwIt != bwItEnd; ++bwIt) {
+		bool hasColor = false;
+		for (Common::MacResIDArray::const_iterator colorIt = colorIDs.begin(), colorItEnd = colorIDs.end(); colorIt != colorItEnd; ++colorIt) {
+			if ((*colorIt) == (*bwIt)) {
+				hasColor = true;
+				break;
+			}
+		}
+
+		if (!hasColor)
+			bwOnlyIDs.push_back(*bwIt);
+	}
+
+	int numCursorsLoaded = 0;
+	for (int cti = 0; cti < 2; cti++) {
+		const uint32 resType = (cti == 0) ? bwType : colorType;
+		const bool isBW = (cti == 0);
+		const Common::MacResIDArray &resArray = (cti == 0) ? bwOnlyIDs : colorIDs;
+
+		for (size_t i = 0; i < resArray.size(); i++) {
+			Common::SharedPtr<Common::SeekableReadStream> resData(f.resMan->getResource(resType, resArray[i]));
+			if (!resData) {
+				warning("Failed to open cursor resource");
+				return;
+			}
+
+			Common::SharedPtr<Graphics::MacCursor> cursor(new Graphics::MacCursor());
+			// Some CURS resources are 72 bytes instead of the expected 68, make sure they load as the correct format
+			if (!cursor->readFromStream(*resData, isBW, 0xff, isBW)) {
+				warning("Failed to load cursor resource");
+				return;
+			}
+
+			cursorGraphics.addMacCursor(resArray[i], cursor);
+			numCursorsLoaded++;
+		}
+	}
+
+	if (numCursorsLoaded == 0) {
+		// If an extension is in detection, it should either have cursors or be categorized as Special if it has some other use.
+		warning("Expected to find cursors in '%s' but there were none.", f.fileName.c_str());
+	}
+}
+
+static bool loadCursorsWin(FileIdentification &f, CursorGraphicCollection &cursorGraphics) {
+	Common::SharedPtr<Common::SeekableReadStream> stream = f.stream;
+
+	if (!stream) {
+		Common::SharedPtr<Common::File> file(new Common::File());
+		if (!file->open(f.fileName))
+			error("Failed to open file '%s'", f.fileName.c_str());
+
+		stream = file;
+	}
+
+	Common::SharedPtr<Common::WinResources> winRes(Common::WinResources::createFromEXE(stream.get()));
+	if (!winRes) {
+		warning("Couldn't load resources from PE file");
+		return false;
+	}
+
+	int numCursorGroupsLoaded = 0;
+	Common::Array<Common::WinResourceID> cursorGroupIDs = winRes->getIDList(Common::kWinGroupCursor);
+	for (Common::Array<Common::WinResourceID>::const_iterator it = cursorGroupIDs.begin(), itEnd = cursorGroupIDs.end(); it != itEnd; ++it) {
+		const Common::WinResourceID &id = *it;
+
+		Common::SharedPtr<Graphics::WinCursorGroup> cursorGroup(Graphics::WinCursorGroup::createCursorGroup(winRes.get(), *it));
+		if (!winRes) {
+			warning("Couldn't load cursor group");
+			return false;
+		}
+
+		if (cursorGroup->cursors.size() == 0) {
+			// Empty?
+			continue;
+		}
+
+		cursorGraphics.addWinCursorGroup(id.getID(), cursorGroup);
+		numCursorGroupsLoaded++;
+	}
+
+	if (numCursorGroupsLoaded == 0) {
+		// If an extension is in detection, it should either have cursors or be categorized as Special if it has some other use.
+		warning("Expected to find cursors in '%s' but there were none.", f.fileName.c_str());
+	}
+
+	return true;
+}
+
+} // namespace Boot
+
+Common::SharedPtr<ProjectDescription> bootProject(const MTropolisGameDescription &gameDesc) {
+	Common::SharedPtr<ProjectDescription> desc;
+
+	Common::Array<Common::SharedPtr<ProjectPersistentResource>> persistentResources;
+
+	Common::SharedPtr<Boot::GameDataHandler> gameDataHandler;
+
+	switch (gameDesc.gameID) {
+	case GID_OBSIDIAN:
+		gameDataHandler.reset(new Boot::ObsidianGameDataHandler(gameDesc));
+		break;
+	default:
+		gameDataHandler.reset(new Boot::GameDataHandler());
+		break;
+	}
+
+	if (gameDesc.desc.platform == Common::kPlatformMacintosh) {
+		Common::Array<Boot::FileIdentification> macFiles;
+
+		debug(1, "Attempting to boot Macintosh game...");
+
+		const ADGameFileDescription *fileDesc = gameDesc.desc.filesDescriptions;
+		while (fileDesc->fileName) {
+			const char *fileName = fileDesc->fileName;
+			
+			Boot::FileIdentification ident;
+			ident.fileName = fileName;
+			ident.category = Boot::kFileCategoryUnknown;
+			if (!Boot::getMacTypesForFile(fileName, ident.macType, ident.macCreator))
+				error("Couldn't determine Mac file type code for file '%s'", fileName);
+
+			macFiles.push_back(ident);
+
+			fileDesc++;
+		}
+
+		gameDataHandler->unpackAdditionalFiles(persistentResources, macFiles);
+		gameDataHandler->categorizeSpecialFiles(macFiles);
+
+		Common::sort(macFiles.begin(), macFiles.end(), Boot::fileSortCompare);
+
+		// File types changed in mTropolis 2.0 in a way that MFmx and MFxm have different meaning than mTropolis 1.0.
+		// So, we need to detect what variety of files we have available:
+		// MT1 Mac: MFmm[+MFmx]
+		// MT2 Mac: MFmm[+MFxm]
+		// MT2 Cross: MFmx[+MFxx]
+		bool haveAnyMFmm = false;
+		bool haveAnyMFmx = false;
+		bool haveAnyMFxx = false;
+		bool haveAnyMFxm = false;
+
+		for (Boot::FileIdentification &macFile : macFiles) {
+			if (macFile.category == Boot::kFileCategoryUnknown) {
+				switch (macFile.macType) {
+				case MKTAG('M', 'F', 'm', 'm'):
+					haveAnyMFmm = true;
+					break;
+				case MKTAG('M', 'F', 'm', 'x'):
+					haveAnyMFmx = true;
+					break;
+				case MKTAG('M', 'F', 'x', 'm'):
+					haveAnyMFxm = true;
+					break;
+				case MKTAG('M', 'F', 'x', 'x'):
+					haveAnyMFxx = true;
+					break;
+				default:
+					break;
+				};
+			}
+		}
+
+		bool isMT2CrossPlatform = (haveAnyMFmx && !haveAnyMFmm);
+		if (isMT2CrossPlatform && haveAnyMFxm)
+			error("Unexpected combination of player file types");		
+
+		// Identify unknown files
+		for (Boot::FileIdentification &macFile : macFiles) {
+			if (macFile.category == Boot::kFileCategoryUnknown) {
+				switch (macFile.macType) {
+				case MKTAG('M', 'F', 'm', 'm'):
+					macFile.category = Boot::kFileCategoryProjectMainSegment;
+					break;
+				case MKTAG('M', 'F', 'm', 'x'):
+					macFile.category = isMT2CrossPlatform ? Boot::kFileCategoryProjectMainSegment : Boot::kFileCategoryProjectAdditionalSegment;
+					break;
+				case MKTAG('M', 'F', 'x', 'm'):
+				case MKTAG('M', 'F', 'x', 'x'):
+					macFile.category = Boot::kFileCategoryProjectAdditionalSegment;
+					break;
+				case MKTAG('A', 'P', 'P', 'L'):
+					macFile.category = Boot::kFileCategoryPlayer;
+					break;
+				case MKTAG('M', 'F', 'c', 'o'):
+				case MKTAG('M', 'F', 'c', 'r'):
+				case MKTAG('M', 'F', 'X', 'O'):
+					macFile.category = Boot::kFileCategoryExtension;
+					break;
+				default:
+					error("Failed to categorize input file '%s'", macFile.fileName.c_str());
+					break;
+				};
+			}
+		}
+
+		Boot::FileIdentification *mainSegmentFile = nullptr;
+		Common::Array<Boot::FileIdentification *> segmentFiles;
+
+		// Bin segments
+		for (Boot::FileIdentification &macFile : macFiles) {
+			switch (macFile.category) {
+			case Boot::kFileCategoryProjectMainSegment:
+				mainSegmentFile = &macFile;
+				break;
+			case Boot::kFileCategoryProjectAdditionalSegment: {
+					int segmentID = Boot::resolveFileSegmentID(macFile.fileName);
+					if (segmentID < 2)
+						error("Unusual segment numbering scheme");
+
+					size_t segmentIndex = static_cast<size_t>(segmentID - 1);
+					while (segmentFiles.size() <= segmentIndex)
+						segmentFiles.push_back(nullptr);
+					segmentFiles[segmentIndex] = &macFile;
+				} break;
+			}
+		}
+
+		if (segmentFiles.size() > 0)
+			segmentFiles[0] = mainSegmentFile;
+		else
+			segmentFiles.push_back(mainSegmentFile);
+
+		// Load cursors
+		Common::SharedPtr<CursorGraphicCollection> cursorGraphics(new CursorGraphicCollection());
+
+		for (Boot::FileIdentification &macFile : macFiles) {
+			if (macFile.category == Boot::kFileCategoryPlayer)
+				Boot::loadCursorsMac(macFile, *cursorGraphics);
+		}
+		
+		for (Boot::FileIdentification &macFile : macFiles) {
+			if (macFile.category == Boot::kFileCategoryExtension)
+				Boot::loadCursorsMac(macFile, *cursorGraphics);
+		}
+
+		// Create the project description
+		desc.reset(new ProjectDescription(isMT2CrossPlatform ? KProjectPlatformCrossPlatform : kProjectPlatformMacintosh));
+
+		for (Boot::FileIdentification *segmentFile : segmentFiles) {
+			if (!segmentFile)
+				error("Missing segment file");
+
+			Common::SharedPtr<Common::SeekableReadStream> dataFork;
+
+			if (segmentFile->stream)
+				dataFork = segmentFile->stream;
+			else {
+				Boot::initResManForFile(*segmentFile);
+				dataFork.reset(segmentFile->resMan->getDataFork());
+				if (!dataFork)
+					error("Segment file '%s' has no data fork", segmentFile->fileName.c_str());
+
+				persistentResources.push_back(Boot::PersistentResource<Common::MacResManager>::wrap(segmentFile->resMan));
+			}
+
+			persistentResources.push_back(Boot::PersistentResource<Common::SeekableReadStream>::wrap(dataFork));
+
+			desc->addSegment(0, dataFork.get());
+		}
+
+		gameDataHandler->addPlugIns(*desc, macFiles);
+
+		desc->setCursorGraphics(cursorGraphics);
+	} else if (gameDesc.desc.platform == Common::kPlatformWindows) {
+		Common::Array<Boot::FileIdentification> winFiles;
+
+		debug(1, "Attempting to boot Windows game...");
+
+		const ADGameFileDescription *fileDesc = gameDesc.desc.filesDescriptions;
+		while (fileDesc->fileName) {
+			const char *fileName = fileDesc->fileName;
+
+			Boot::FileIdentification ident;
+			ident.fileName = fileName;
+			ident.category = Boot::kFileCategoryUnknown;
+			ident.macType = 0;
+			ident.macCreator = 0;
+			winFiles.push_back(ident);
+
+			fileDesc++;
+		}
+
+		gameDataHandler->unpackAdditionalFiles(persistentResources, winFiles);
+		gameDataHandler->categorizeSpecialFiles(winFiles);
+
+		Common::sort(winFiles.begin(), winFiles.end(), Boot::fileSortCompare);
+
+		bool isCrossPlatform = false;
+		bool isWindows = false;
+		bool isMT1 = false;
+		bool isMT2 = false;
+
+		// Identify unknown files
+		for (Boot::FileIdentification &winFile : winFiles) {
+			if (winFile.category == Boot::kFileCategoryUnknown) {
+				switch (Boot::getWinFileEndingPseudoTag(winFile.fileName)) {
+				case MKTAG('.', 'm', 'p', 'l'):
+					winFile.category = Boot::kFileCategoryProjectMainSegment;
+					isWindows = true;
+					isMT1 = true;
+					if (isMT2)
+						error("Unexpected mix of file platforms");
+					break;
+				case MKTAG('.', 'm', 'p', 'x'):
+					winFile.category = Boot::kFileCategoryProjectAdditionalSegment;
+					isWindows = true;
+					isMT1 = true;
+					if (isMT2)
+						error("Unexpected mix of file platforms");
+					break;
+
+				case MKTAG('.', 'm', 'f', 'w'):
+					winFile.category = Boot::kFileCategoryProjectMainSegment;
+					if (isMT1 || isCrossPlatform)
+						error("Unexpected mix of file platforms");
+					isWindows = true;
+					isMT2 = true;
+					break;
+
+				case MKTAG('.', 'm', 'x', 'w'):
+					winFile.category = Boot::kFileCategoryProjectAdditionalSegment;
+					if (isMT1 || isCrossPlatform)
+						error("Unexpected mix of file platforms");
+					isWindows = true;
+					isMT2 = true;
+					break;
+
+				case MKTAG('.', 'm', 'f', 'x'):
+					winFile.category = Boot::kFileCategoryProjectMainSegment;
+					if (isWindows)
+						error("Unexpected mix of file platforms");
+					isCrossPlatform = true;
+					isMT2 = true;
+					break;
+
+				case MKTAG('.', 'm', 'x', 'x'):
+					winFile.category = Boot::kFileCategoryProjectAdditionalSegment;
+					if (isWindows)
+						error("Unexpected mix of file platforms");
+					isCrossPlatform = true;
+					isMT2 = true;
+					break;
+
+				case MKTAG('.', 'c', '9', '5'):
+				case MKTAG('.', 'e', '9', '5'):
+				case MKTAG('.', 'r', '9', '5'):
+					winFile.category = Boot::kFileCategoryExtension;
+					break;
+
+				case MKTAG('.', 'e', 'x', 'e'):
+					winFile.category = Boot::kFileCategoryPlayer;
+					break;
+
+				default:
+					error("Failed to categorize input file '%s'", winFile.fileName.c_str());
+					break;
+				};
+			}
+		}
+
+		Boot::FileIdentification *mainSegmentFile = nullptr;
+		Common::Array<Boot::FileIdentification *> segmentFiles;
+
+		// Bin segments
+		for (Boot::FileIdentification &macFile : winFiles) {
+			switch (macFile.category) {
+			case Boot::kFileCategoryProjectMainSegment:
+				mainSegmentFile = &macFile;
+				break;
+			case Boot::kFileCategoryProjectAdditionalSegment: {
+				int segmentID = Boot::resolveFileSegmentID(macFile.fileName);
+				if (segmentID < 2)
+					error("Unusual segment numbering scheme");
+
+				size_t segmentIndex = static_cast<size_t>(segmentID - 1);
+				while (segmentFiles.size() <= segmentIndex)
+					segmentFiles.push_back(nullptr);
+				segmentFiles[segmentIndex] = &macFile;
+			} break;
+			}
+		}
+
+		if (segmentFiles.size() > 0)
+			segmentFiles[0] = mainSegmentFile;
+		else
+			segmentFiles.push_back(mainSegmentFile);
+
+		// Load cursors
+		Common::SharedPtr<CursorGraphicCollection> cursorGraphics(new CursorGraphicCollection());
+
+		for (Boot::FileIdentification &winFile : winFiles) {
+			if (winFile.category == Boot::kFileCategoryPlayer)
+				Boot::loadCursorsWin(winFile, *cursorGraphics);
+		}
+
+		for (Boot::FileIdentification &winFile : winFiles) {
+			if (winFile.category == Boot::kFileCategoryExtension)
+				Boot::loadCursorsWin(winFile, *cursorGraphics);
+		}
+
+		// Create the project description
+		desc.reset(new ProjectDescription(isCrossPlatform ? KProjectPlatformCrossPlatform : kProjectPlatformWindows));
+
+		for (Boot::FileIdentification *segmentFile : segmentFiles) {
+			if (!segmentFile)
+				error("Missing segment file");
+
+			if (segmentFile->stream) {
+				persistentResources.push_back(Boot::PersistentResource<Common::SeekableReadStream>::wrap(segmentFile->stream));
+				desc->addSegment(0, segmentFile->stream.get());
+			} else {
+				desc->addSegment(0, segmentFile->fileName.c_str());
+			}
+		}
+
+		gameDataHandler->addPlugIns(*desc, winFiles);
+
+		desc->setCursorGraphics(cursorGraphics);
+	}
+
+	Common::SharedPtr<ProjectResources> resources(new ProjectResources());
+	resources->persistentResources = persistentResources;
+
+	desc->setResources(resources);
+
+	return desc;
+}
+
+} // End of namespace MTropolis
diff --git a/engines/mtropolis/boot.h b/engines/mtropolis/boot.h
new file mode 100644
index 00000000000..6b664376dd1
--- /dev/null
+++ b/engines/mtropolis/boot.h
@@ -0,0 +1,36 @@
+/* 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 MTROPOLIS_BOOT_H
+#define MTROPOLIS_BOOT_H
+
+#include "common/ptr.h"
+
+namespace MTropolis {
+
+struct MTropolisGameDescription;
+class ProjectDescription;
+
+Common::SharedPtr<ProjectDescription> bootProject(const MTropolisGameDescription &gameDesc);
+
+} // End of namespace MTropolis
+
+#endif
diff --git a/engines/mtropolis/module.mk b/engines/mtropolis/module.mk
index fb738ab377c..d5a34f5e8dc 100644
--- a/engines/mtropolis/module.mk
+++ b/engines/mtropolis/module.mk
@@ -3,6 +3,7 @@ MODULE := engines/mtropolis
 MODULE_OBJS = \
 	asset_factory.o \
 	assets.o \
+	boot.o \
 	core.o \
 	data.o \
 	debug.o \
diff --git a/engines/mtropolis/mtropolis.cpp b/engines/mtropolis/mtropolis.cpp
index 992375d6c95..5c50359bf4a 100644
--- a/engines/mtropolis/mtropolis.cpp
+++ b/engines/mtropolis/mtropolis.cpp
@@ -40,6 +40,7 @@
 #include "mtropolis/mtropolis.h"
 
 #include "mtropolis/actions.h"
+#include "mtropolis/boot.h"
 #include "mtropolis/debug.h"
 #include "mtropolis/runtime.h"
 
@@ -49,330 +50,10 @@
 
 namespace MTropolis {
 
-static Common::SharedPtr<Obsidian::WordGameData> loadWinObsidianWordGameData() {
-	Common::File f;
-	if (!f.open("RSGKit.r95")) {
-		error("Couldn't open word game data file");
-		return nullptr;
-	}
-
-	Common::SharedPtr<Obsidian::WordGameData> wgData(new Obsidian::WordGameData());
-
-	Obsidian::WordGameLoadBucket buckets[] = {
-		{0, 0},             // 0 letters
-		{0x63D54, 0x63D5C}, // 1 letter
-		{0x63BF8, 0x63CA4}, // 2 letter
-		{0x627D8, 0x631E8}, // 3 letter
-		{0x5C2C8, 0x60628}, // 4 letter
-		{0x52F4C, 0x5919C}, // 5 letter
-		{0x47A64, 0x4F2FC}, // 6 letter
-		{0x3BC98, 0x43B20}, // 7 letter
-		{0x2DA78, 0x38410}, // 8 letter
-		{0x218F8, 0x2AA18}, // 9 letter
-		{0x19D78, 0x1FA18}, // 10 letter
-		{0x15738, 0x18BE8}, // 11 letter
-		{0x128A8, 0x14DE8}, // 12 letter
-		{0x1129C, 0x1243C}, // 13 letter
-		{0x10974, 0x110C4}, // 14 letter
-		{0x105EC, 0x108BC}, // 15 letter
-		{0x10454, 0x105A8}, // 16 letter
-		{0x103A8, 0x10434}, // 17 letter
-		{0x10348, 0x10398}, // 18 letter
-		{0, 0},				// 19 letter
-		{0x10328, 0x10340}, // 20 letter
-		{0x102EC, 0x1031C}, // 21 letter
-		{0x102D0, 0x102E8},	// 22 letter
-	};
-
-	if (!wgData->load(&f, buckets, 23, 4, true)) {
-		error("Failed to load word game data file");
-		return nullptr;
-	}
-
-	return wgData;
-}
-
-static bool loadCursorsFromPE(CursorGraphicCollection &cursorGraphics, Common::SeekableReadStream *stream) {
-	Common::SharedPtr<Common::WinResources> winRes(Common::WinResources::createFromEXE(stream));
-	if (!winRes) {
-		warning("Couldn't load resources from PE file");
-		return false;
-	}
-
-	Common::Array<Common::WinResourceID> cursorGroupIDs = winRes->getIDList(Common::kWinGroupCursor);
-	for (Common::Array<Common::WinResourceID>::const_iterator it = cursorGroupIDs.begin(), itEnd = cursorGroupIDs.end(); it != itEnd; ++it) {
-		const Common::WinResourceID &id = *it;
-
-		Common::SharedPtr<Graphics::WinCursorGroup> cursorGroup(Graphics::WinCursorGroup::createCursorGroup(winRes.get(), *it));
-		if (!winRes) {
-			warning("Couldn't load cursor group");
-			return false;
-		}
-
-		if (cursorGroup->cursors.size() == 0) {
-			// Empty?
-			continue;
-		}
-
-		cursorGraphics.addWinCursorGroup(id.getID(), cursorGroup);
-	}
-
-	return true;
-}
-
-static bool loadCursorsFromMacResources(CursorGraphicCollection &cursorGraphics, Common::MacResManager &resMan) {
-	const uint32 bwType = MKTAG('C', 'U', 'R', 'S');
-	const uint32 colorType = MKTAG('c', 'r', 's', 'r');
-
-	Common::MacResIDArray bwIDs = resMan.getResIDArray(bwType);
-	Common::MacResIDArray colorIDs = resMan.getResIDArray(colorType);
-
-	Common::MacResIDArray bwOnlyIDs;
-	for (Common::MacResIDArray::const_iterator bwIt = bwIDs.begin(), bwItEnd = bwIDs.end(); bwIt != bwItEnd; ++bwIt) {
-		bool hasColor = false;
-		for (Common::MacResIDArray::const_iterator colorIt = colorIDs.begin(), colorItEnd = colorIDs.end(); colorIt != colorItEnd; ++colorIt) {
-			if ((*colorIt) == (*bwIt)) {
-				hasColor = true;
-				break;
-			}
-		}
-
-		if (!hasColor)
-			bwOnlyIDs.push_back(*bwIt);
-	}
-
-	for (int cti = 0; cti < 2; cti++) {
-		const uint32 resType = (cti == 0) ? bwType : colorType;
-		const bool isBW = (cti == 0);
-		const Common::MacResIDArray &resArray = (cti == 0) ? bwOnlyIDs : colorIDs;
-
-		for (size_t i = 0; i < resArray.size(); i++) {
-			Common::SeekableReadStream *resData = resMan.getResource(resType, resArray[i]);
-			if (!resData) {
-				warning("Failed to open cursor resource");
-				return false;
-			}
-
-			Common::SharedPtr<Graphics::MacCursor> cursor(new Graphics::MacCursor());
-			// Some CURS resources are 72 bytes instead of the expected 68, make sure they load as the correct format
-			if (!cursor->readFromStream(*resData, isBW, 0xff, isBW)) {
-				warning("Failed to load cursor resource");
-				return false;
-			}
-
-			cursorGraphics.addMacCursor(resArray[i], cursor);
-		}
-	}
-
-	return true;
-}
-
-struct MacObsidianResources : public ProjectResources {
-	MacObsidianResources();
-	~MacObsidianResources();
-
-	void setup(bool haveWordGames);
-	void setup_mac_demo();
-	Common::SeekableReadStream *getSegmentStream(int index) const;
-	const Common::SharedPtr<CursorGraphicCollection> &getCursorGraphics() const;
-	const Common::SharedPtr<Obsidian::WordGameData> &getWordGameData() const;
-
-private:
-	Common::MacResManager _installerResMan;
-	Common::MacResManager _dataFileResMan[5];
-
-	Common::SeekableReadStream *_installerDataForkStream;
-	Common::Archive *_installerArchive;
-	Common::SeekableReadStream *_segmentStreams[6];
-
-	Common::SharedPtr<CursorGraphicCollection> _cursorGraphics;
-	Common::SharedPtr<Obsidian::WordGameData> _wordGameData;
-};
-
-MacObsidianResources::MacObsidianResources() : _installerArchive(nullptr), _installerDataForkStream(nullptr) {
-	for (int i = 0; i < 6; i++)
-		_segmentStreams[i] = nullptr;
-
-	_cursorGraphics.reset(new CursorGraphicCollection());
-}
-
-void MacObsidianResources::setup(bool haveWordGames) {
-	debug(1, "Opening Obsidian Mac installer package...");
-
-	if (!_installerResMan.open("Obsidian Installer"))
-		error("Failed to open Obsidian Installer");
-
-	if (!_installerResMan.hasDataFork())
-		error("Obsidian Installer has no data fork");
-
-	_installerDataForkStream = _installerResMan.getDataFork();
-
-	_installerArchive = Common::createStuffItArchive(_installerDataForkStream);
-	if (!_installerArchive)
-		error("Failed to open Obsidian Installer archive");
-
-	debug(1, "Reading data from installer...");
-	_segmentStreams[0] = _installerArchive->createReadStreamForMember("Obsidian Data 1");
-
-	debug(1, "Opening data segments...");
-	for (int i = 0; i < 5; i++) {
-		char fileName[32];
-		sprintf(fileName, "Obsidian Data %i", (i + 2));
-
-		Common::MacResManager &resMan = _dataFileResMan[i];
-		if (!resMan.open(fileName))
-			error("Failed to open data file %s", fileName);
-
-		if (!resMan.hasDataFork())
-			error("Data fork in %s is missing", fileName);
-
-		_segmentStreams[1 + i] = resMan.getDataFork();
-	}
-
-	debug(1, "Opening resources...");
-
-	const char *cursorSources[4] = {"Obsidian", "Basic.rPP", "mCursors.cPP", "Obsidian.cPP"};
-	for (int i = 0; i < 4; i++) {
-		const char *fileName = cursorSources[i];
-
-		Common::MacResManager resMan;
-		if (!resMan.open(Common::Path(fileName), *_installerArchive))
-			error("Failed to open resources in file '%s'", fileName);
-
-		if (!loadCursorsFromMacResources(*_cursorGraphics, resMan))
-			error("Failed to read cursor resources from file '%s'", fileName);
-	}
-
-	if (haveWordGames) {
-		debug(1, "Loading word games...");
-
-		{
-			Common::ArchiveMemberPtr rsgKit = _installerArchive->getMember(Common::Path("RSGKit.rPP"));
-			if (!rsgKit)
-				error("Couldn't find word game file in installer archive");
-
-			_wordGameData.reset(new Obsidian::WordGameData());
-
-			Common::SharedPtr<Common::SeekableReadStream> stream(rsgKit->createReadStream());
-			if (!stream)
-				error("Failed to open word game file");
-
-			Obsidian::WordGameLoadBucket buckets[] = {
-				{0, 0},             // 0 letters
-				{0xD7C8, 0xD7CC},   // 1 letter
-				{0xD7CC, 0xD84D},   // 2 letter
-				{0xD84D, 0xE25D},   // 3 letter
-				{0x1008C, 0x12AA8}, // 4 letter
-				{0x14C58, 0x19614}, // 5 letter
-				{0x1C73C, 0x230C1}, // 6 letter
-				{0x26D10, 0x2EB98}, // 7 letter
-				{0x32ADC, 0x3AA0E}, // 8 letter
-				{0x3E298, 0x45B88}, // 9 letter
-				{0x48BE8, 0x4E0D0}, // 10 letter
-				{0x4FFB0, 0x53460}, // 11 letter
-				{0x545F0, 0x56434}, // 12 letter
-				{0x56D84, 0x57CF0}, // 13 letter
-				{0x58158, 0x58833}, // 14 letter
-				{0x58A08, 0x58CD8}, // 15 letter
-				{0x58D8C, 0x58EAD}, // 16 letter
-				{0x58EF4, 0x58F72}, // 17 letter
-				{0x58F90, 0x58FDC}, // 18 letter
-				{0, 0},             // 19 letter
-				{0x58FEC, 0x59001}, // 20 letter
-				{0x59008, 0x59034}, // 21 letter
-				{0x5903C, 0x59053}, // 22 letter
-			};
-
-			if (!_wordGameData->load(stream.get(), buckets, 23, 1, false))
-				error("Failed to load word game data");
-		}
-	}
-
-	debug(1, "Finished unpacking installer resources");
-}
-
-void MacObsidianResources::setup_mac_demo() {
-	debug(1, "Opening resources...");
-
-	const char *cursorSources[4] = {"Obsidian Demo", "Basic.rPP", "mCursors.cPP", "Obsidian.cPP"};
-	for (int i = 0; i < 4; i++) {
-		const char *fileName = cursorSources[i];
-
-		Common::MacResManager resMan;
-		if (!resMan.open(Common::Path(fileName)))
-			error("Failed to open resources in file '%s'", fileName);
-
-		if (!loadCursorsFromMacResources(*_cursorGraphics, resMan))
-			error("Failed to read cursor resources from file '%s'", fileName);
-	}
-	debug(1, "Loading word games...");
-
-	{
-		Common::MacResManager resMan;
-		if (!resMan.open(Common::Path("RSGKit.rPP")))
-			error("Couldn't find word game file in installer archive");
-
-		_wordGameData.reset(new Obsidian::WordGameData());
-
-		Common::SeekableReadStream *stream = resMan.getDataFork();
-		if (!stream)
-			error("Failed to open word game file");
-
-		Obsidian::WordGameLoadBucket buckets[] = {
-			{0, 0},				// 0 letters
-			{0xD7C8, 0xD7CC},	// 1 letter
-			{0xD7CC, 0xD84D},	// 2 letter
-			{0xD84D, 0xE25D},	// 3 letter
-			{0x1008C, 0x12AA8},	// 4 letter
-			{0x14C58, 0x19614},	// 5 letter
-			{0x1C73C, 0x230C1},	// 6 letter
-			{0x26D10, 0x2EB98},	// 7 letter
-			{0x32ADC, 0x3AA0E},	// 8 letter
-			{0x3E298, 0x45B88},	// 9 letter
-			{0x48BE8, 0x4E0D0},	// 10 letter
-			{0x4FFB0, 0x53460},	// 11 letter
-			{0x545F0, 0x56434},	// 12 letter
-			{0x56D84, 0x57CF0}, // 13 letter
-			{0x58158, 0x58833}, // 14 letter
-			{0x58A08, 0x58CD8}, // 15 letter
-			{0x58D8C, 0x58EAD}, // 16 letter
-			{0x58EF4, 0x58F72}, // 17 letter
-			{0x58F90, 0x58FDC},	// 18 letter
-			{0, 0},				// 19 letter
-			{0x58FEC, 0x59001},	// 20 letter
-			{0x59008, 0x59034},	// 21 letter
-			{0x5903C, 0x59053},	// 22 letter
-		};
-
-		if (!_wordGameData->load(stream, buckets, 23, 1, false))
-			error("Failed to load word game data");
-	}
-	debug(1, "Finished loading demo resources");
-}
-
-Common::SeekableReadStream *MacObsidianResources::getSegmentStream(int index) const {
-	return _segmentStreams[index];
-}
-
-const Common::SharedPtr<CursorGraphicCollection> &MacObsidianResources::getCursorGraphics() const {
-	return _cursorGraphics;
-}
-
-const Common::SharedPtr<Obsidian::WordGameData>& MacObsidianResources::getWordGameData() const {
-	return _wordGameData;
-}
-
-MacObsidianResources::~MacObsidianResources() {
-	for (int i = 0; i < 6; i++)
-		delete _segmentStreams[i];
-
-	delete _installerArchive;
-	delete _installerDataForkStream;
-}
-
 MTropolisEngine::MTropolisEngine(OSystem *syst, const MTropolisGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc) {
 	const Common::FSNode gameDataDir(ConfMan.get("path"));
 	SearchMan.addSubDirectoryMatching(gameDataDir, "Resource");
+
 	if (gameDesc->gameID == GID_OBSIDIAN && _gameDescription->desc.platform == Common::kPlatformWindows) {
 		SearchMan.addSubDirectoryMatching(gameDataDir, "Obsidian");
 		SearchMan.addSubDirectoryMatching(gameDataDir, "Obsidian/RESOURCE");
@@ -429,130 +110,19 @@ Common::Error MTropolisEngine::run() {
 
 	_runtime.reset(new Runtime(_system, _mixer, this, this));
 
-	if (_gameDescription->gameID == GID_OBSIDIAN && _gameDescription->desc.platform == Common::kPlatformWindows) {
-		preferredWidth = 640;
-		preferredHeight = 480;
-		preferredColorDepthMode = kColorDepthMode16Bit;
-		enhancedColorDepthMode = kColorDepthMode32Bit;
-
-		_runtime->getHacks().ignoreMismatchedProjectNameInObjectLookups = true;
-
-		_runtime->addVolume(0, "Installed", true);
-		_runtime->addVolume(1, "OBSIDIAN1", true);
-		_runtime->addVolume(2, "OBSIDIAN2", true);
-		_runtime->addVolume(3, "OBSIDIAN3", true);
-		_runtime->addVolume(4, "OBSIDIAN4", true);
-		_runtime->addVolume(5, "OBSIDIAN5", true);
-
-		Common::SharedPtr<ProjectDescription> desc(new ProjectDescription(kProjectPlatformWindows));
+	Common::SharedPtr<ProjectDescription> projectDesc = bootProject(*_gameDescription);
 
-		if (_gameDescription->desc.flags & ADGF_DEMO) {
-			if (Common::File::exists("OBSIDI~1.MPL")) {
-				desc->addSegment(0, "OBSIDI~1.MPL");
-			} else {
-				desc->addSegment(0, "OBSIDIAN DEMO DATA.MPL");
-			}
-		} else {
-			desc->addSegment(0, "Obsidian Data 1.MPL");
-			desc->addSegment(1, "Obsidian Data 2.MPX");
-			desc->addSegment(2, "Obsidian Data 3.MPX");
-			desc->addSegment(3, "Obsidian Data 4.MPX");
-			desc->addSegment(4, "Obsidian Data 5.MPX");
-			desc->addSegment(5, "Obsidian Data 6.MPX");
-
-			Common::SharedPtr<Obsidian::WordGameData> wgData = loadWinObsidianWordGameData();
-			desc->addPlugIn(PlugIns::createObsidian(wgData));
-		}
-
-		Common::SharedPtr<CursorGraphicCollection> cursors(new CursorGraphicCollection());
-		{
-			const char *cursorSources[3];
-			// Has to be in this order, some resources from MCURSORS will clobber resources from the player executable
-			if (Common::File::exists("OBSIDIAN4.C95")) {
-				cursorSources[1] = "OBSIDIAN4.C95";
-			} else {
-				cursorSources[1] = "MCURSORS.C95";
-			}
-
-			if (Common::File::exists("OBSIDIAN DEMO.EXE")) {
-				cursorSources[0] = "OBSIDIAN DEMO.EXE";
-			} else {
-				cursorSources[0] = "OBSIDIAN.EXE";
-			}
-			if (!(_gameDescription->desc.flags & ADGF_DEMO)) {
-				cursorSources[2] = "Obsidian.c95";
-			}
-
-			for (int i = 0; i < (_gameDescription->desc.flags & ADGF_DEMO ? 2 : 3); i++) {
-				const char *fileName = cursorSources[i];
-				Common::File f;
-				if (!f.open(Common::Path(fileName)))
-					error("Failed to open resources in file '%s'", fileName);
-
-				if (!loadCursorsFromPE(*cursors, &f))
-					error("Failed to read cursor resources from file '%s'", fileName);
-			}
-		}
-
-		Common::SharedPtr<Obsidian::WordGameData> wgData;
-
-		// Non-English releases don't have Bureau word game puzzles
-		if (_gameDescription->desc.language == Common::Language::EN_ANY)
-			wgData = loadWinObsidianWordGameData();
-
-		desc->setCursorGraphics(cursors);
-
-		Common::SharedPtr<MTropolis::PlugIn> standardPlugIn = PlugIns::createStandard();
-		static_cast<Standard::StandardPlugIn *>(standardPlugIn.get())->getHacks().allowGarbledListModData = true;
-		desc->addPlugIn(standardPlugIn);
-
-		_runtime->queueProject(desc);
-
-	} else if (_gameDescription->gameID == GID_OBSIDIAN && _gameDescription->desc.platform == Common::kPlatformMacintosh) {
+	if (_gameDescription->gameID == GID_OBSIDIAN) {
 		preferredWidth = 640;
 		preferredHeight = 480;
 		preferredColorDepthMode = kColorDepthMode16Bit;
 		enhancedColorDepthMode = kColorDepthMode32Bit;
 
 		_runtime->getHacks().ignoreMismatchedProjectNameInObjectLookups = true;
-
-		MacObsidianResources *resources = new MacObsidianResources();
-		Common::SharedPtr<ProjectResources> resPtr(resources);
-
-		_runtime->addVolume(0, "Installed", true);
-		_runtime->addVolume(1, "OBSIDIAN1", true);
-		_runtime->addVolume(2, "OBSIDIAN2", true);
-		_runtime->addVolume(3, "OBSIDIAN3", true);
-		_runtime->addVolume(4, "OBSIDIAN4", true);
-		_runtime->addVolume(5, "OBSIDIAN5", true);
-
-		Common::SharedPtr<ProjectDescription> desc(new ProjectDescription(kProjectPlatformMacintosh));
-
-		if (_gameDescription->desc.flags & ADGF_DEMO) {
-			resources->setup_mac_demo();
-
-			desc->addSegment(0, "Obs Demo Large w Sega");
-		} else {
-			// Non-English releases don't have Bureau word game puzzles
-			resources->setup(_gameDescription->desc.language == Common::Language::EN_ANY);
-
-			for (int i = 0; i < 6; i++)
-				desc->addSegment(i, resources->getSegmentStream(i));
-		}
-
-
-		Common::SharedPtr<MTropolis::PlugIn> standardPlugIn = PlugIns::createStandard();
-		static_cast<Standard::StandardPlugIn *>(standardPlugIn.get())->getHacks().allowGarbledListModData = true;
-		desc->addPlugIn(standardPlugIn);
-
-		desc->addPlugIn(PlugIns::createObsidian(resources->getWordGameData()));
-
-		desc->setResources(resPtr);
-		desc->setCursorGraphics(resources->getCursorGraphics());
-
-		_runtime->queueProject(desc);
 	}
 
+	_runtime->queueProject(projectDesc);
+
 	// Figure out pixel formats
 	Graphics::PixelFormat modePixelFormats[kColorDepthModeCount];
 	bool haveExactMode[kColorDepthModeCount];
diff --git a/engines/mtropolis/runtime.cpp b/engines/mtropolis/runtime.cpp
index a6353710907..8bf8309b19e 100644
--- a/engines/mtropolis/runtime.cpp
+++ b/engines/mtropolis/runtime.cpp
@@ -2113,7 +2113,14 @@ void IPlugInModifierRegistrar::registerPlugInModifier(const char *name, const IP
 PlugIn::~PlugIn() {
 }
 
+ProjectPersistentResource::~ProjectPersistentResource() {
+}
+
 ProjectResources::~ProjectResources() {
+	// We need these destroyed in reverse order exactly, and unfortunately the ScummVM Common::Array destructor
+	// destroys forward
+	while (persistentResources.size() > 0)
+		persistentResources.pop_back();
 }
 
 CursorGraphic::~CursorGraphic() {
@@ -3525,7 +3532,8 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, ISaveUIProvider *saveProv
 	_displayWidth(1024), _displayHeight(768), _realTimeBase(0), _playTimeBase(0), _sceneTransitionState(kSceneTransitionStateNotTransitioning),
 	_lastFrameCursor(nullptr), _defaultCursor(new DefaultCursorGraphic()), _platform(kProjectPlatformUnknown),
 	_cachedMousePosition(Common::Point(0, 0)), _realMousePosition(Common::Point(0, 0)), _trackedMouseOutside(false),
-	_forceCursorRefreshOnce(true), _haveModifierOverrideCursor(false), _sceneGraphChanged(false), _isQuitting(false), _collisionCheckTime(0) {
+	_forceCursorRefreshOnce(true), _haveModifierOverrideCursor(false), _sceneGraphChanged(false), _isQuitting(false),
+	  _collisionCheckTime(0), _defaultVolumeState(true) {
 	_random.reset(new Common::RandomSource("mtropolis"));
 
 	_vthread.reset(new VThread());
@@ -5294,6 +5302,11 @@ bool Runtime::getVolumeState(const Common::String &name, int &outVolumeID, bool
 		}
 	}
 
+	if (_defaultVolumeState) {
+		outIsMounted = true;
+		return true;
+	}
+
 	return false;
 }
 
diff --git a/engines/mtropolis/runtime.h b/engines/mtropolis/runtime.h
index 4c951f9293d..2cc7692e029 100644
--- a/engines/mtropolis/runtime.h
+++ b/engines/mtropolis/runtime.h
@@ -1126,8 +1126,15 @@ public:
 	virtual void registerModifiers(IPlugInModifierRegistrar *registrar) const = 0;
 };
 
+class ProjectPersistentResource {
+public:
+	virtual ~ProjectPersistentResource();
+};
+
 struct ProjectResources {
 	virtual ~ProjectResources();
+
+	Common::Array<Common::SharedPtr<ProjectPersistentResource> > persistentResources;
 };
 
 class CursorGraphic {
@@ -1177,6 +1184,7 @@ enum ProjectPlatform {
 
 	kProjectPlatformWindows,
 	kProjectPlatformMacintosh,
+	KProjectPlatformCrossPlatform,
 };
 
 class ProjectDescription {
@@ -1457,6 +1465,7 @@ public:
 
 	void addVolume(int volumeID, const char *name, bool isMounted);
 	bool getVolumeState(const Common::String &name, int &outVolumeID, bool &outIsMounted) const;
+	void setDefaultVolumeState(bool defaultState);
 
 	void addSceneStateTransition(const HighLevelSceneTransition &transition);
 
@@ -1742,6 +1751,8 @@ private:
 	uint32 _modifierOverrideCursorID;
 	bool _haveModifierOverrideCursor;
 
+	bool _defaultVolumeState;
+
 	// True if any elements were added to the scene, removed from the scene, or reparented since last draw
 	bool _sceneGraphChanged;
 




More information about the Scummvm-git-logs mailing list