[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