[Scummvm-git-logs] scummvm master -> cf97b636f3c24b4253fe75de27353f5f07ec6c4d
elasota
noreply at scummvm.org
Thu Dec 14 03:07:13 UTC 2023
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
8aadc2fcdf MTROPOLIS: New boot loader and VFS
cab8f4cae9 MTROPOLIS: Fix up Obsidian Japanese
cf97b636f3 MTROPOLIS: Fix SPQR boot
Commit: 8aadc2fcdff76c500617cc78f001cf57e301a690
https://github.com/scummvm/scummvm/commit/8aadc2fcdff76c500617cc78f001cf57e301a690
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2023-12-13T22:06:27-05:00
Commit Message:
MTROPOLIS: New boot loader and VFS
Changed paths:
A engines/mtropolis/vfs.cpp
A engines/mtropolis/vfs.h
engines/mtropolis/boot.cpp
engines/mtropolis/boot.h
engines/mtropolis/data.cpp
engines/mtropolis/data.h
engines/mtropolis/detection.cpp
engines/mtropolis/detection.h
engines/mtropolis/miniscript.cpp
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
index 380ab5169f0..fc5b15977ac 100644
--- a/engines/mtropolis/boot.cpp
+++ b/engines/mtropolis/boot.cpp
@@ -35,6 +35,7 @@
#include "mtropolis/detection.h"
#include "mtropolis/runtime.h"
#include "mtropolis/subtitles.h"
+#include "mtropolis/vfs.h"
#include "mtropolis/plugin/mti.h"
#include "mtropolis/plugin/obsidian.h"
@@ -47,12 +48,13 @@ namespace MTropolis {
namespace Boot {
class GameDataHandler;
+class BootScriptContext;
struct ManifestSubtitlesDef {
- const char *linesTablePath;
- const char *speakerTablePath;
- const char *assetMappingTablePath;
- const char *modifierMappingTablePath;
+ Common::String speakerTablePath;
+ Common::String linesTablePath;
+ Common::String assetMappingTablePath;
+ Common::String modifierMappingTablePath;
};
enum ManifestFileType {
@@ -72,10 +74,7 @@ struct ManifestFile {
struct Game {
MTropolisGameBootID bootID;
- const ManifestFile *manifest;
- const char **directories;
- const ManifestSubtitlesDef *subtitlesDef;
- GameDataHandler *(*gameDataFactory)(const Boot::Game &game, const MTropolisGameDescription &desc);
+ void (BootScriptContext::*bootFunction)();
};
template<class T>
@@ -107,14 +106,6 @@ FileIdentification::FileIdentification() : category(MTFT_AUTO) {
macCreator.value = 0;
}
-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:
GameDataHandler(const Boot::Game &game, const MTropolisGameDescription &desc);
@@ -176,6 +167,8 @@ public:
void categorizeSpecialFiles(Common::Array<FileIdentification> &files) override;
void addPlugIns(ProjectDescription &projectDesc, const Common::Array<FileIdentification> &files) override;
+ static Common::SharedPtr<MTropolis::PlugIn> loadPlugIn(Common::Archive &fs, const Common::Path &pluginsLocation, bool isMac, bool isRetail, bool isEnglish);
+
private:
bool _isMac;
bool _isRetail;
@@ -185,8 +178,8 @@ private:
void unpackMacRetailInstaller(Common::Array<Common::SharedPtr<ProjectPersistentResource> > &persistentResources, Common::Array<FileIdentification> &files);
void unpackGermanWinRetailInstaller(Common::Array<Common::SharedPtr<ProjectPersistentResource> > &persistentResources, Common::Array<FileIdentification> &files);
- Common::SharedPtr<Obsidian::WordGameData> loadWinWordGameData();
- Common::SharedPtr<Obsidian::WordGameData> loadMacWordGameData();
+ static Common::SharedPtr<Obsidian::WordGameData> loadWinWordGameData(Common::SeekableReadStream *stream);
+ static Common::SharedPtr<Obsidian::WordGameData> loadMacWordGameData(Common::SeekableReadStream *stream);
Common::SharedPtr<Common::Archive> _installerArchive;
};
@@ -330,10 +323,11 @@ void ObsidianGameDataHandler::addPlugIns(ProjectDescription &projectDesc, const
Common::SharedPtr<Obsidian::WordGameData> wgData;
if (_isRetail && _isEnglish) {
- if (_isMac)
- wgData = loadMacWordGameData();
- else
- wgData = loadWinWordGameData();
+ if (_isMac) {
+ wgData = loadMacWordGameData(nullptr);
+ } else {
+ wgData = loadWinWordGameData(nullptr);
+ }
}
Common::SharedPtr<Obsidian::ObsidianPlugIn> obsidianPlugIn(new Obsidian::ObsidianPlugIn(wgData));
@@ -344,16 +338,36 @@ void ObsidianGameDataHandler::addPlugIns(ProjectDescription &projectDesc, const
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<MTropolis::PlugIn> ObsidianGameDataHandler::loadPlugIn(Common::Archive &fs, const Common::Path &pluginsLocation, bool isMac, bool isRetail, bool isEnglish) {
+ Common::SharedPtr<Obsidian::WordGameData> wgData;
- Common::SharedPtr<Obsidian::WordGameData> wgData(new Obsidian::WordGameData());
+ if (isRetail && isEnglish) {
+ if (isMac) {
+ Common::MacResManager resMan;
- Common::SharedPtr<Common::SeekableReadStream> stream(rsgKit->createReadStream());
- if (!stream)
- error("Failed to open word game file");
+ Common::SharedPtr<Common::SeekableReadStream> dataStream(resMan.openFileOrDataFork(pluginsLocation.appendComponent("RSGKit.rPP"), fs));
+
+ if (!dataStream)
+ error("Failed to open word game data");
+
+ wgData = loadMacWordGameData(dataStream.get());
+ } else {
+ Common::SharedPtr<Common::SeekableReadStream> stream(fs.createReadStreamForMember(pluginsLocation.appendComponent("RSGKit.r95")));
+
+ if (!stream)
+ error("Failed to open word game data");
+
+ wgData = loadWinWordGameData(stream.get());
+ }
+
+ }
+
+ Common::SharedPtr<Obsidian::ObsidianPlugIn> obsidianPlugIn(new Obsidian::ObsidianPlugIn(wgData));
+ return obsidianPlugIn.staticCast<MTropolis::PlugIn>();
+}
+
+Common::SharedPtr<Obsidian::WordGameData> ObsidianGameDataHandler::loadMacWordGameData(Common::SeekableReadStream *stream) {
+ Common::SharedPtr<Obsidian::WordGameData> wgData(new Obsidian::WordGameData());
Obsidian::WordGameLoadBucket buckets[] = {
{0, 0}, // 0 letters
@@ -381,19 +395,13 @@ Common::SharedPtr<Obsidian::WordGameData> ObsidianGameDataHandler::loadMacWordGa
{0x5903C, 0x59053}, // 22 letter
};
- if (!wgData->load(stream.get(), buckets, 23, 1, false))
+ if (!wgData->load(stream, 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> ObsidianGameDataHandler::loadWinWordGameData(Common::SeekableReadStream *stream) {
Common::SharedPtr<Obsidian::WordGameData> wgData(new Obsidian::WordGameData());
Obsidian::WordGameLoadBucket buckets[] = {
@@ -422,7 +430,7 @@ Common::SharedPtr<Obsidian::WordGameData> ObsidianGameDataHandler::loadWinWordGa
{0x102D0, 0x102E8}, // 22 letter
};
- if (!wgData->load(&f, buckets, 23, 4, true)) {
+ if (!wgData->load(stream, buckets, 23, 4, true)) {
error("Failed to load word game data file");
return nullptr;
}
@@ -550,50 +558,6 @@ void STTGSGameDataHandler::addPlugIns(ProjectDescription &projectDesc, const Com
projectDesc.addPlugIn(standardPlugIn);
}
-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;
- 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())
@@ -613,14 +577,17 @@ static bool fileSortCompare(const FileIdentification &a, const FileIdentificatio
return aSize < b.fileName.size();
}
-static void loadCursorsMac(FileIdentification &f, CursorGraphicCollection &cursorGraphics) {
- initResManForFile(f);
+static void loadCursorsMac(Common::Archive &archive, const Common::Path &path, CursorGraphicCollection &cursorGraphics) {
+ Common::MacResManager resMan;
+
+ if (!resMan.open(path, archive))
+ return;
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 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) {
@@ -643,7 +610,7 @@ static void loadCursorsMac(FileIdentification &f, CursorGraphicCollection &curso
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]));
+ Common::SharedPtr<Common::SeekableReadStream> resData(resMan.getResource(resType, resArray[i]));
if (!resData) {
warning("Failed to open cursor resource");
return;
@@ -660,29 +627,17 @@ static void loadCursorsMac(FileIdentification &f, CursorGraphicCollection &curso
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());
+static bool loadCursorsWin(Common::Archive &archive, const Common::Path &path, CursorGraphicCollection &cursorGraphics) {
+ Common::SharedPtr<Common::SeekableReadStream> stream(archive.createReadStreamForMember(path));
- stream = file;
- }
+ if (!stream)
+ error("Failed to open file '%s'", path.toString(archive.getPathSeparator()).c_str());
Common::SharedPtr<Common::WinResources> winRes(Common::WinResources::createFromEXE(stream.get()));
- if (!winRes) {
- warning("Couldn't load resources from PE file '%s'", f.fileName.c_str());
+ if (!winRes)
return false;
- }
int numCursorGroupsLoaded = 0;
Common::Array<Common::WinResourceID> cursorGroupIDs = winRes->getIDList(Common::kWinGroupCursor);
@@ -704,922 +659,2203 @@ static bool loadCursorsWin(FileIdentification &f, CursorGraphicCollection &curso
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 Games {
+class BootScriptParser {
+public:
+ enum TokenType {
+ kTokenTypeBooleanConstant,
+ kTokenTypeOctalConstant,
+ kTokenTypeHexConstant,
+ kTokenTypeFloatConstant,
+ kTokenTypeDecimalConstant,
+ kTokenTypeIdentifier,
+ kTokenTypePunctuation,
+ kTokenTypeString,
+ kTokenTypeChar,
+ };
-const ManifestFile obsidianRetailMacEnFiles[] = {
- {"Obsidian Installer", MTFT_SPECIAL},
- {"Obsidian Data 2", MTFT_ADDITIONAL},
- {"Obsidian Data 3", MTFT_ADDITIONAL},
- {"Obsidian Data 4", MTFT_ADDITIONAL},
- {"Obsidian Data 5", MTFT_ADDITIONAL},
- {"Obsidian Data 6", MTFT_ADDITIONAL},
- {nullptr, MTFT_AUTO}
-};
+ enum ExprType {
+ kExprTypeIdentifier,
+ kExprTypeIntegral,
+ kExprTypeFloat,
+ kExprTypeString,
+ kExprTypeChar,
+ kExprTypePunctuation,
+ kExprTypeBoolean,
+ };
-const ManifestFile obsidianRetailMacJpFiles[] = {
- {"xn--u9j9ecg0a2fsa1io6k6jkdc2k", MTFT_SPECIAL},
- {"Obsidian Data 2", MTFT_ADDITIONAL},
- {"Obsidian Data 3", MTFT_ADDITIONAL},
- {"Obsidian Data 4", MTFT_ADDITIONAL},
- {"Obsidian Data 5", MTFT_ADDITIONAL},
- {"Obsidian Data 6", MTFT_ADDITIONAL},
- {nullptr, MTFT_AUTO}
-};
+ explicit BootScriptParser(Common::ReadStream &stream);
-const ManifestFile obsidianDemoMacEnFiles[] = {
- {"Obsidian Demo", MTFT_PLAYER},
- {"Basic.rPP", MTFT_EXTENSION},
- {"Experimental.rPP", MTFT_EXTENSION},
- {"Extras.rPP", MTFT_EXTENSION},
- {"mCursors.cPP", MTFT_EXTENSION},
- {"mNet.rPP", MTFT_EXTENSION},
- {"Obsidian.cPP", MTFT_EXTENSION},
- {"RSGKit.rPP", MTFT_SPECIAL},
- {"Obs Demo Large w Sega", MTFT_MAIN},
- {nullptr, MTFT_AUTO}
-};
+ bool readToken(Common::String &outToken);
-const ManifestFile obsidianRetailWinEnFiles[] = {
- {"Obsidian.exe", MTFT_PLAYER},
- {"Obsidian.c95", MTFT_EXTENSION},
- {"MCURSORS.C95", MTFT_EXTENSION},
- {"RSGKit.r95", MTFT_SPECIAL},
- {"Obsidian Data 1.MPL", MTFT_MAIN},
- {"Obsidian Data 2.MPX", MTFT_ADDITIONAL},
- {"Obsidian Data 3.MPX", MTFT_ADDITIONAL},
- {"Obsidian Data 4.MPX", MTFT_ADDITIONAL},
- {"Obsidian Data 5.MPX", MTFT_ADDITIONAL},
- {"Obsidian Data 6.MPX", MTFT_ADDITIONAL},
- {nullptr, MTFT_AUTO}
-};
+ void expect(const char *token);
-const ManifestFile obsidianDemoWinEnFiles1[] = {
- {"OBSIDIAN.EXE", MTFT_PLAYER},
- {"OBSIDIAN.R95", MTFT_EXTENSION},
- {"TEXTWORK.R95", MTFT_EXTENSION},
- {"EXPRMNTL.R95", MTFT_EXTENSION},
- {"MCURSORS.C95", MTFT_EXTENSION},
- {"OBSIDIAN DEMO DATA.MPL", MTFT_MAIN},
- {nullptr, MTFT_AUTO}
-};
+ static TokenType classifyToken(const Common::String &token);
+ static ExprType tokenTypeToExprType(TokenType tt);
-const ManifestFile obsidianDemoWinEnFiles2[] = {
- {"OBSIDIAN.EXE", MTFT_PLAYER},
- {"OBSIDIAN.R95", MTFT_EXTENSION},
- {"TEXTWORK.R95", MTFT_EXTENSION},
- {"EXPRMNTL.R95", MTFT_EXTENSION},
- {"MCURSORS.C95", MTFT_EXTENSION},
- {"OBSIDI~1.MPL", MTFT_MAIN},
- {nullptr, MTFT_AUTO}
-};
+ static Common::String evalString(const Common::String &token);
+ static uint evalIntegral(const Common::String &token);
-const ManifestFile obsidianDemoWinEnFiles3[] = {
- {"OBSIDIAN DEMO.EXE", MTFT_PLAYER},
- {"OBSIDIAN1.R95", MTFT_EXTENSION},
- {"OBSIDIAN2.R95", MTFT_EXTENSION},
- {"OBSIDIAN3.R95", MTFT_EXTENSION},
- {"OBSIDIAN4.C95", MTFT_EXTENSION},
- {"OBSIDIAN DEMO DATA.MPL", MTFT_MAIN},
- {nullptr, MTFT_AUTO}
+private:
+ bool readChar(char &c);
+ void requeueChar(char c);
+
+ void skipLineComment();
+ bool skipBlockComment();
+
+ bool parseNumber(char firstChar, Common::String &outToken);
+ bool parseIdentifier(Common::String &outToken);
+ bool parseQuotedString(char quoteChar, Common::String &outToken);
+ bool parseFloatFractionalPart(Common::String &outToken); // Parses the part after the '.'
+ bool parseFloatExponentPart(Common::String &outToken); // Parses the part after the 'e'
+ bool parseHexDigits(Common::String &outToken); // Must parse at least 1
+ bool parseOctalDigits(Common::String &outToken);
+ bool checkFloatSuffix();
+
+ static bool isIdentifierInitialChar(char c);
+ static bool isIdentifierChar(char c);
+ static bool isDigit(char c);
+ static bool isAlpha(char c);
+
+ static uint evalOctalIntegral(const Common::String &token);
+ static uint evalDecimalIntegral(const Common::String &token);
+ static uint evalHexIntegral(const Common::String &token);
+
+ static char evalEscapeSequence(const Common::String &token, uint startPos, uint maxEndPos, uint &outLength);
+ static char evalOctalEscapeSequence(const Common::String &token, uint startPos, uint maxEndPos, uint &outLength);
+ static char evalHexEscapeSequence(const Common::String &token, uint startPos, uint maxEndPos, uint &outLength);
+
+ Common::ReadStream &_stream;
+ char _requeuedChars[2];
+ int _numRequeuedChars;
+ bool _isEOS;
};
-const ManifestFile obsidianDemoWinEnFiles4[] = {
- {"OBSIDIAN.EXE", MTFT_PLAYER},
- {"OBSIDIAN.R95", MTFT_EXTENSION},
- {"TEXTWORK.R95", MTFT_EXTENSION},
- {"EXPRMNTL.R95", MTFT_EXTENSION},
- {"MCURSORS.C95", MTFT_EXTENSION},
- {"OBSIDIAN.MPL", MTFT_MAIN},
- {nullptr, MTFT_AUTO}
-};
-const ManifestFile obsidianDemoWinEnFiles5[] = {
- {"OBSIDI~1.EXE", MTFT_PLAYER},
- {"OBSIDIAN.R95", MTFT_EXTENSION},
- {"TEXTWORK.R95", MTFT_EXTENSION},
- {"EXPRMNTL.R95", MTFT_EXTENSION},
- {"MCURSORS.C95", MTFT_EXTENSION},
- {"OBSIDI~1.MPL", MTFT_MAIN},
- {nullptr, MTFT_AUTO}
-};
+BootScriptParser::BootScriptParser(Common::ReadStream &stream) : _stream(stream), _requeuedChars{0, 0}, _numRequeuedChars(0), _isEOS(false) {
+}
-const ManifestFile obsidianDemoWinEnFiles6[] = {
- {"OBSIDIAN.EXE", MTFT_PLAYER},
- {"OBSIDIAN.R95", MTFT_EXTENSION},
- {"TEXTWORK.R95", MTFT_EXTENSION},
- {"EXPRMNTL.R95", MTFT_EXTENSION},
- {"MCURSORS.C95", MTFT_EXTENSION},
- {"OBSIDIAN DEMO DATA.MPL", MTFT_MAIN},
- {nullptr, MTFT_AUTO}
-};
+bool BootScriptParser::readToken(Common::String &outToken) {
+ // Skip whitespace
+ char firstChar = 0;
+ char secondChar = 0;
-const ManifestFile obsidianDemoWinEnFiles7[] = {
- {"OBSIDIAN DEMO.EXE", MTFT_PLAYER},
- {"OBSIDIAN1.R95", MTFT_EXTENSION},
- {"OBSIDIAN2.R95", MTFT_EXTENSION},
- {"OBSIDIAN3.R95", MTFT_EXTENSION},
- {"OBSIDIAN4.C95", MTFT_EXTENSION},
- {"OBSIDIAN DEMO DATA.MPL", MTFT_MAIN},
- {nullptr, MTFT_AUTO}
-};
+ for (;;) {
+ if (!readChar(firstChar))
+ return false;
-const ManifestFile obsidianRetailWinDeInstalledFiles[] = {
- {"Obsidian.exe", MTFT_PLAYER},
- {"Obsidian.c95", MTFT_EXTENSION},
- {"MCURSORS.C95", MTFT_EXTENSION},
- {"Obsidian Data 1.MPL", MTFT_MAIN},
- {"Obsidian Data 2.MPX", MTFT_ADDITIONAL},
- {"Obsidian Data 3.MPX", MTFT_ADDITIONAL},
- {"Obsidian Data 4.MPX", MTFT_ADDITIONAL},
- {"Obsidian Data 5.MPX", MTFT_ADDITIONAL},
- {"Obsidian Data 6.MPX", MTFT_ADDITIONAL},
- {nullptr, MTFT_AUTO}
-};
+ if (firstChar == '/') {
+ if (!readChar(secondChar)) {
+ outToken = "/";
+ return true;
+ }
-const ManifestFile obsidianRetailWinDeDiscFiles[] = {
- {"_SETUP.1", MTFT_SPECIAL},
- {"Obsidian Data 1.MPL", MTFT_MAIN},
- {"Obsidian Data 2.MPX", MTFT_ADDITIONAL},
- {"Obsidian Data 3.MPX", MTFT_ADDITIONAL},
- {"Obsidian Data 4.MPX", MTFT_ADDITIONAL},
- {"Obsidian Data 5.MPX", MTFT_ADDITIONAL},
- {"Obsidian Data 6.MPX", MTFT_ADDITIONAL},
- {nullptr, MTFT_AUTO}
-};
+ if (secondChar == '/') {
+ skipLineComment();
+ } else if (secondChar == '*') {
+ if (!skipBlockComment())
+ return false;
+ } else {
+ requeueChar(secondChar);
+ return true;
+ }
-const ManifestFile obsidianRetailWinItFiles[] = {
- {"Obsidian.exe", MTFT_PLAYER},
- {"Obsidian.c95", MTFT_EXTENSION},
- {"MCURSORS.C95", MTFT_EXTENSION},
- {"RSGKit.r95", MTFT_SPECIAL},
- {"Obsidian Data 1.MPL", MTFT_MAIN},
- {"Obsidian Data 2.MPX", MTFT_ADDITIONAL},
- {"Obsidian Data 3.MPX", MTFT_ADDITIONAL},
- {"Obsidian Data 4.MPX", MTFT_ADDITIONAL},
- {"Obsidian Data 5.MPX", MTFT_ADDITIONAL},
- {"Obsidian Data 6.MPX", MTFT_ADDITIONAL},
- {nullptr, MTFT_AUTO}
-};
+ continue;
+ }
-const char *obsidianRetailWinDirectories[] = {
- "Obsidian",
- "Obsidian/RESOURCE",
- "RESOURCE",
- nullptr
-};
+ // Ignore whitespace
+ if (firstChar >= 0 && firstChar <= 32)
+ continue;
-const ManifestSubtitlesDef obsidianRetailEnSubtitlesDef = {
- "subtitles_lines_obsidian_en.csv",
- "subtitles_speakers_obsidian_en.csv",
- "subtitles_asset_mapping_obsidian_en.csv",
+ if (isDigit(firstChar))
+ return parseNumber(firstChar, outToken);
- // Modifier mapping is the same for both Mac and Win retail, since the MIDI GUIDs are all identical.
- "subtitles_modifier_mapping_obsidian_en.csv"
-};
+ if (isIdentifierInitialChar(firstChar)) {
+ requeueChar(firstChar);
+ return parseIdentifier(outToken);
+ }
-const ManifestFile mtiRetailMacFiles[] = {
- {"mPlayer PPC", MTFT_PLAYER},
- {"Group3.rPP", MTFT_EXTENSION},
- {"MTIKit.rPP", MTFT_EXTENSION},
- {"xn--MTI1-8b7a", MTFT_MAIN},
- {"MTI2", MTFT_ADDITIONAL},
- {"MTI3", MTFT_ADDITIONAL},
- {"MTI4", MTFT_ADDITIONAL},
- {nullptr, MTFT_AUTO}
-};
+ if (firstChar == '\'' || firstChar == '\"')
+ return parseQuotedString(firstChar, outToken);
+
+ if (firstChar == '.') {
+ if (readChar(secondChar)) {
+ if (secondChar == '.') {
+ char thirdChar = 0;
+ if (readChar(thirdChar)) {
+ if (thirdChar == '.') {
+ outToken = "...";
+ return true;
+ } else {
+ requeueChar(thirdChar);
+ }
+ }
+ } else if (isDigit(secondChar)) {
+ Common::String fractionalPart;
+ if (!parseFloatFractionalPart(fractionalPart))
+ return false;
+
+ outToken = Common::String('.') + fractionalPart;
+ return true;
+ } else {
+ requeueChar(secondChar);
+ }
+ }
+ outToken = ".";
+ return true;
+ }
-const char *mtiRetailMacDirectories[] = {
- "MPlayer PPC",
- "MPlayer PPC/Resource",
- nullptr
-};
+ if (firstChar == ':') {
+ if (readChar(secondChar)) {
+ if (secondChar == ':') {
+ outToken = "::";
+ return true;
+ } else {
+ requeueChar(secondChar);
+ }
+ }
-const ManifestFile mtiRetailWinFiles[] = {
- {"MTPLAY32.EXE", MTFT_PLAYER},
- {"GROUP3.R95", MTFT_EXTENSION},
- {"MTIKIT.R95", MTFT_EXTENSION},
- {"MTI1.MPL", MTFT_MAIN},
- {"MTI2.MPX", MTFT_ADDITIONAL},
- {"MTI3.MPX", MTFT_ADDITIONAL},
- {"MTI4.MPX", MTFT_ADDITIONAL},
- {"1.AVI", MTFT_VIDEO},
- {"2.AVI", MTFT_VIDEO},
- {"3.AVI", MTFT_VIDEO},
- {"4.AVI", MTFT_VIDEO},
- {"5.AVI", MTFT_VIDEO},
- {"6.AVI", MTFT_VIDEO},
- {"7.AVI", MTFT_VIDEO},
- {"8.AVI", MTFT_VIDEO},
- {"9.AVI", MTFT_VIDEO},
- {"10.AVI", MTFT_VIDEO},
- {nullptr, MTFT_AUTO}
-};
+ outToken = ":";
+ return true;
+ }
-const ManifestFile mtiDemoWinFiles[] = {
- {"MTIWIN95.EXE", MTFT_PLAYER},
- {"GROUP3.R95", MTFT_EXTENSION},
- {"MTIKIT.R95", MTFT_EXTENSION},
- {"MUP_DATA.MPL", MTFT_MAIN},
- {nullptr, MTFT_AUTO}
-};
+ switch (firstChar) {
+ case '+':
+ case '-':
+ case '=':
+ case '<':
+ case '>':
+ case '|':
+ case '&':
+ if (!readChar(secondChar)) {
+ outToken = Common::String(firstChar);
+ return true;
+ } else if (secondChar == firstChar || secondChar == '=') {
+ char constructedString[2] = {firstChar, secondChar};
+ outToken = Common::String(constructedString, 2);
+ return true;
+ } else {
+ requeueChar(secondChar);
+ outToken = Common::String(firstChar);
+ }
-const char *mtiRetailWinDirectories[] = {
- "MTPLAY32",
- "MTPLAY32/RESOURCE",
- "VIDEO",
- nullptr
-};
+ return true;
-const ManifestFile albert1RetailWinDeFiles[] = {
- {"Albert.exe", MTFT_PLAYER},
- {"album411.MPL", MTFT_MAIN},
- {"album412.MPX", MTFT_ADDITIONAL},
- {"BASIC.X95", MTFT_SPECIAL},
- {"BITMAP.R95", MTFT_SPECIAL},
- {"EXTRAS.R95", MTFT_SPECIAL},
- {"ROTATORK.R95", MTFT_SPECIAL},
- {nullptr, MTFT_AUTO},
-};
+ case '*':
+ case '/':
+ case '%':
+ case '!':
+ case '^':
+ if (!readChar(secondChar)) {
+ outToken = Common::String(firstChar);
+ return true;
+ } else if (secondChar == '=') {
+ char constructedString[2] = {firstChar, secondChar};
+ outToken = Common::String(constructedString, 2);
+ return true;
+ } else {
+ requeueChar(secondChar);
+ outToken = Common::String(firstChar);
+ }
-const char *albert1RetailWinDeDirectories[] = {
- "ALBERT",
- "ALBERT/DATA",
- "ALBERT/DATA/RESOURCE",
- "DATA",
- "DATA/RESOURCE",
- nullptr
-};
+ return true;
+ case ',':
+ case ';':
+ case '?':
+ case '[':
+ case ']':
+ case '(':
+ case ')':
+ case '{':
+ case '}':
+ outToken = Common::String(firstChar);
+ return true;
+ default:
+ error("Unrecognized token in boot script: %c", firstChar);
+ return false;
+ };
+ }
+}
-const ManifestFile albert2RetailWinDeFiles[] = {
- {"reise.exe", MTFT_PLAYER},
- {"voyage1.mpl", MTFT_MAIN},
- {"voyage2.mpx", MTFT_ADDITIONAL},
- {"BASIC.X95", MTFT_SPECIAL},
- {"BITMAP.R95", MTFT_SPECIAL},
- {"EXTRAS.R95", MTFT_SPECIAL},
- {"ROTATORK.R95", MTFT_SPECIAL},
- {nullptr, MTFT_AUTO},
-};
+void BootScriptParser::expect(const char *expectedToken) {
+ Common::String token;
+ if (!readToken(token))
+ error("Expected '%s' but found EOF", expectedToken);
-const char *albert2RetailWinDeDirectories[] = {
- "REISE",
- "REISE/DATA",
- "REISE/DATA/RESOURCE",
- "DATA",
- "DATA/RESOURCE",
- nullptr
-};
+ if (token != expectedToken)
+ error("Expected '%s' but found '%s'", expectedToken, token.c_str());
+}
-const ManifestFile albert3RetailWinDeFiles[] = {
- {"insel.exe", MTFT_PLAYER},
- {"ile_myst1.mpl", MTFT_MAIN},
- {"ILEMYST2.MPX", MTFT_ADDITIONAL},
- {"BASIC.X95", MTFT_SPECIAL},
- {"BITMAP.R95", MTFT_SPECIAL},
- {"EXTRAS.R95", MTFT_SPECIAL},
- {"ROTATORK.R95", MTFT_SPECIAL},
- {nullptr, MTFT_AUTO},
-};
+BootScriptParser::TokenType BootScriptParser::classifyToken(const Common::String &token) {
+ if (token.size() == 0 || token == "." || token == "...")
+ return kTokenTypePunctuation;
-const char *albert3RetailWinDeDirectories[] = {
- "DATA",
- "DATA/RESOURCE",
- nullptr
-};
+ if (token[0] == '.')
+ return kTokenTypeFloatConstant;
-const ManifestFile spqrRetailWinEnFiles[] = {
- {"SPQR32.EXE", MTFT_PLAYER},
- {"MCURSORS.C95", MTFT_EXTENSION},
- {"SPQR.MPL", MTFT_MAIN},
- {"S_6842.MPX", MTFT_ADDITIONAL},
- {nullptr, MTFT_AUTO}
-};
+ if (isDigit(token[0])) {
+ if (token.size() > 1 && (token[1] == 'x' || token[1] == 'X'))
+ return kTokenTypeHexConstant;
-const char *spqrRetailWinDirectories[] = {
- "RESOURCE",
- nullptr
-};
+ for (char c : token) {
+ if (c == '.' || c == 'f' || c == 'F' || c == 'e' || c == 'E')
+ return kTokenTypeFloatConstant;
+ }
-const ManifestFile spqrRetailMacEnFiles[] = {
- {"Install.vct", MTFT_SPECIAL},
- {"S_6772", MTFT_ADDITIONAL},
- {nullptr, MTFT_AUTO}
-};
+ if (token[0] == '0')
+ return kTokenTypeOctalConstant;
-const char *spqrRetailMacDirectories[] = {
- "GAME",
- nullptr
-};
+ return kTokenTypeDecimalConstant;
+ }
-const ManifestFile sttgsDemoWinFiles[] = {
- {"MTPLAY95.EXE", MTFT_PLAYER},
- {"Trektriv.mpl", MTFT_MAIN},
- {nullptr, MTFT_AUTO}
-};
+ if (isIdentifierInitialChar(token[0])) {
+ if (token == "true" || token == "false")
+ return kTokenTypeBooleanConstant;
-const ManifestFile unitWinFiles[] = {
- {"UNIT32.EXE", MTFT_PLAYER},
- {"DATA.MFX", MTFT_MAIN},
- {"CURSORS.C32", MTFT_EXTENSION},
- {"BASIC.X32", MTFT_SPECIAL},
- {"EXTRAS.R32", MTFT_SPECIAL},
- {nullptr, MTFT_AUTO}
-};
+ return kTokenTypeIdentifier;
+ }
-const char *unitWinDirectories[] = {
- "MPLUGINS",
- nullptr
-};
+ if (token[0] == '\'')
+ return kTokenTypeChar;
-const Game games[] = {
- // Obsidian - Retail - Macintosh - English
- {
- MTBOOT_OBSIDIAN_RETAIL_MAC_EN,
- obsidianRetailMacEnFiles,
- nullptr,
- &obsidianRetailEnSubtitlesDef,
- GameDataHandlerFactory<ObsidianGameDataHandler>::create
- },
- // Obsidian - Retail - Macintosh - Japanese
- {
- MTBOOT_OBSIDIAN_RETAIL_MAC_JP,
- obsidianRetailMacJpFiles,
- nullptr,
- nullptr,
- GameDataHandlerFactory<ObsidianGameDataHandler>::create
- },
- // Obsidian - Retail - Windows - English
- {
- MTBOOT_OBSIDIAN_RETAIL_WIN_EN,
- obsidianRetailWinEnFiles,
- obsidianRetailWinDirectories,
- &obsidianRetailEnSubtitlesDef,
- GameDataHandlerFactory<ObsidianGameDataHandler>::create
- },
- // Obsidian - Retail - Windows - German - Installed
- {
- MTBOOT_OBSIDIAN_RETAIL_WIN_DE_INSTALLED,
- obsidianRetailWinDeInstalledFiles,
- obsidianRetailWinDirectories,
- nullptr,
- GameDataHandlerFactory<ObsidianGameDataHandler>::create
- },
- // Obsidian - Retail - Windows - German - Disc
- {
- MTBOOT_OBSIDIAN_RETAIL_WIN_DE_DISC,
- obsidianRetailWinDeDiscFiles,
- nullptr,
- nullptr,
- GameDataHandlerFactory<ObsidianGameDataHandler>::create
- },
- // Obsidian - Retail - Windows - Italian
- {
- MTBOOT_OBSIDIAN_RETAIL_WIN_IT,
- obsidianRetailWinItFiles,
- obsidianRetailWinDirectories,
- nullptr,
- GameDataHandlerFactory<ObsidianGameDataHandler>::create
- },
- // Obsidian - Demo - Macintosh - English
- {
- MTBOOT_OBSIDIAN_DEMO_MAC_EN,
- obsidianDemoMacEnFiles,
- nullptr,
- nullptr,
- GameDataHandlerFactory<ObsidianGameDataHandler>::create
- },
- // Obsidian - Demo - Windows - English - Variant 1
- {
- MTBOOT_OBSIDIAN_DEMO_WIN_EN_1,
- obsidianDemoWinEnFiles1,
- nullptr,
- nullptr,
- GameDataHandlerFactory<ObsidianGameDataHandler>::create
- },
- // Obsidian - Demo - Windows - English - Variant 2
- {
- MTBOOT_OBSIDIAN_DEMO_WIN_EN_2,
- obsidianDemoWinEnFiles2,
- nullptr,
- nullptr,
- GameDataHandlerFactory<ObsidianGameDataHandler>::create
- },
- // Obsidian - Demo - Windows - English - Variant 3
- {
- MTBOOT_OBSIDIAN_DEMO_WIN_EN_3,
- obsidianDemoWinEnFiles3,
- nullptr,
- nullptr,
- GameDataHandlerFactory<ObsidianGameDataHandler>::create
- },
- // Obsidian - Demo - Windows - English - Variant 4
- {
- MTBOOT_OBSIDIAN_DEMO_WIN_EN_4,
- obsidianDemoWinEnFiles4,
- nullptr,
- nullptr,
- GameDataHandlerFactory<ObsidianGameDataHandler>::create
- },
- // Obsidian - Demo - Windows - English - Variant 5
- {
- MTBOOT_OBSIDIAN_DEMO_WIN_EN_5,
- obsidianDemoWinEnFiles5,
- nullptr,
- nullptr,
- GameDataHandlerFactory<ObsidianGameDataHandler>::create
- },
- // Obsidian - Demo - Windows - English - Variant 6
- {
- MTBOOT_OBSIDIAN_DEMO_WIN_EN_6,
- obsidianDemoWinEnFiles6,
- nullptr,
- nullptr,
- GameDataHandlerFactory<ObsidianGameDataHandler>::create
- },
- // Obsidian - Demo - Windows - English - Variant 7
- {
- MTBOOT_OBSIDIAN_DEMO_WIN_EN_7,
- obsidianDemoWinEnFiles7,
- nullptr,
- nullptr,
- GameDataHandlerFactory<ObsidianGameDataHandler>::create
- },
- // Muppet Treasure Island - Retail - Macintosh - Multiple languages
- {
- MTBOOT_MTI_RETAIL_MAC,
- mtiRetailMacFiles,
- mtiRetailMacDirectories,
- nullptr,
- GameDataHandlerFactory<MTIGameDataHandler>::create
- },
- // Muppet Treasure Island - Retail - Windows - Multiple languages
- {
- MTBOOT_MTI_RETAIL_WIN,
- mtiRetailWinFiles,
- mtiRetailWinDirectories,
- nullptr,
- GameDataHandlerFactory<MTIGameDataHandler>::create
- },
- // Muppet Treasure Island - Demo - Windows
- {
- MTBOOT_MTI_DEMO_WIN,
- mtiDemoWinFiles,
- nullptr,
- nullptr,
- GameDataHandlerFactory<MTIGameDataHandler>::create
- },
- // Uncle Albert's Magical Album - German - Windows
- {
- MTBOOT_ALBERT1_WIN_DE,
- albert1RetailWinDeFiles,
- albert1RetailWinDeDirectories,
- nullptr,
- GameDataHandlerFactory<AlbertGameDataHandler>::create
- },
- // Uncle Albert's Fabulous Voyage - German - Windows
- {
- MTBOOT_ALBERT2_WIN_DE,
- albert2RetailWinDeFiles,
- albert2RetailWinDeDirectories,
- nullptr,
- GameDataHandlerFactory<AlbertGameDataHandler>::create
- },
- // Uncle Albert's Mysterious Island - German - Windows
- {
- MTBOOT_ALBERT3_WIN_DE,
- albert3RetailWinDeFiles,
- albert3RetailWinDeDirectories,
- nullptr,
- GameDataHandlerFactory<AlbertGameDataHandler>::create
- },
- // SPQR: The Empire's Darkest Hour - Retail - Windows - English
- {
- MTBOOT_SPQR_RETAIL_WIN,
- spqrRetailWinEnFiles,
- spqrRetailWinDirectories,
- nullptr,
- GameDataHandlerFactory<SPQRGameDataHandler>::create
- },
- // SPQR: The Empire's Darkest Hour - Retail - Macintosh - English
- {
- MTBOOT_SPQR_RETAIL_MAC,
- spqrRetailMacEnFiles,
- spqrRetailMacDirectories,
- nullptr,
- GameDataHandlerFactory<SPQRGameDataHandler>::create
- },
- // Star Trek: The Game Show - Demo - Windows
- {
- MTBOOT_STTGS_DEMO_WIN,
- sttgsDemoWinFiles,
- nullptr,
- nullptr,
- GameDataHandlerFactory<STTGSGameDataHandler>::create
- },
- // Unit: Rebooted
- {
- MTBOOT_UNIT_REBOOTED_WIN,
- unitWinFiles,
- unitWinDirectories,
- nullptr,
- GameDataHandlerFactory<STTGSGameDataHandler>::create
- },
-};
+ if (token[0] == '\"')
+ return kTokenTypeString;
-} // End of namespace Games
+ return kTokenTypePunctuation;
+}
-} // End of namespace Boot
+BootScriptParser::ExprType BootScriptParser::tokenTypeToExprType(BootScriptParser::TokenType tt) {
+ switch (tt) {
+ case kTokenTypeBooleanConstant:
+ return kExprTypeBoolean;
+ case kTokenTypeChar:
+ return kExprTypeChar;
+ case kTokenTypeDecimalConstant:
+ case kTokenTypeOctalConstant:
+ case kTokenTypeHexConstant:
+ return kExprTypeIntegral;
+ case kTokenTypeFloatConstant:
+ return kExprTypeFloat;
+ case kTokenTypeString:
+ return kExprTypeString;
+ default:
+ return kExprTypePunctuation;
+ }
-Common::SharedPtr<ProjectDescription> bootProject(const MTropolisGameDescription &gameDesc) {
- Common::SharedPtr<ProjectDescription> desc;
+ return kExprTypeString;
+}
- Common::Array<Common::SharedPtr<ProjectPersistentResource>> persistentResources;
+Common::String BootScriptParser::evalString(const Common::String &token) {
+ assert(token.size() >= 2);
+ assert(token[0] == '\"');
+ assert(token[token.size() - 1] == '\"');
- Common::SharedPtr<Boot::GameDataHandler> gameDataHandler;
+ uint endPos = token.size() - 1;
- Common::SharedPtr<SubtitleAssetMappingTable> subsAssetMappingTable;
- Common::SharedPtr<SubtitleModifierMappingTable> subsModifierMappingTable;
- Common::SharedPtr<SubtitleSpeakerTable> subsSpeakerTable;
- Common::SharedPtr<SubtitleLineTable> subsLineTable;
+ Common::Array<char> chars;
+ chars.resize(token.size() - 2);
- Common::String speakerTablePath;
- Common::String linesTablePath;
- Common::String assetMappingTablePath;
- Common::String modifierMappingTablePath;
+ uint numChars = 0;
- const Boot::Game *bootGame = nullptr;
- for (const Boot::Game &bootGameCandidate : Boot::Games::games) {
- if (bootGameCandidate.bootID == gameDesc.bootID) {
- // Multiple manifests should not have the same manifest ID!
- assert(!bootGame);
- bootGame = &bootGameCandidate;
+ for (uint i = 1; i < endPos; i++) {
+ char c = token[i];
+ if (c == '\\') {
+ uint escapeLength = 0;
+
+ c = evalEscapeSequence(token, i + 1, endPos, escapeLength);
+ i += escapeLength;
}
+
+ chars[numChars++] = c;
}
- if (!bootGame)
- error("Couldn't boot mTropolis game, don't have a file manifest for manifest ID %i", static_cast<int>(gameDesc.bootID));
+ if (numChars == 0)
+ return "";
- if (bootGame->gameDataFactory)
- gameDataHandler.reset(bootGame->gameDataFactory(*bootGame, gameDesc));
- else
- gameDataHandler.reset(new Boot::GameDataHandler(*bootGame, gameDesc));
+ return Common::String(&chars[0], numChars);
+}
+uint BootScriptParser::evalIntegral(const Common::String &token) {
+ if (token.size() == 1)
+ return evalDecimalIntegral(token);
- if (bootGame->subtitlesDef) {
- linesTablePath = bootGame->subtitlesDef->linesTablePath;
- speakerTablePath = bootGame->subtitlesDef->speakerTablePath;
- assetMappingTablePath = bootGame->subtitlesDef->assetMappingTablePath;
- modifierMappingTablePath = bootGame->subtitlesDef->modifierMappingTablePath;
+ if (token[1] == 'x' || token[1] == 'X')
+ return evalHexIntegral(token);
+
+ if (token[0] == '0')
+ return evalOctalIntegral(token);
+
+ return evalDecimalIntegral(token);
+}
+
+bool BootScriptParser::readChar(char &c) {
+ if (_numRequeuedChars > 0) {
+ _numRequeuedChars--;
+ c = _requeuedChars[_numRequeuedChars];
+ return true;
+ }
+
+ if (_isEOS)
+ return false;
+
+ if (_stream.read(&c, 1) == 0) {
+ _isEOS = true;
+ return false;
+ }
+
+ return true;
+}
+
+void BootScriptParser::requeueChar(char c) {
+ assert(_numRequeuedChars < sizeof(_requeuedChars));
+ _requeuedChars[_numRequeuedChars++] = c;
+}
+
+void BootScriptParser::skipLineComment() {
+ char ch = 0;
+
+ while (readChar(ch)) {
+ if (ch == '\r') {
+ if (readChar(ch)) {
+ if (ch != '\n')
+ requeueChar(ch);
+ }
+
+ return;
+ }
+
+ if (ch == '\n')
+ return;
+ }
+}
+
+bool BootScriptParser::skipBlockComment() {
+ char ch = 0;
+
+ while (readChar(ch)) {
+ if (ch == '*') {
+ if (readChar(ch)) {
+ if (ch == '/')
+ return true;
+ else
+ requeueChar(ch);
+ }
+ }
+ }
+
+ warning("Unexpected EOF in boot script block comment!");
+ return false;
+}
+
+bool BootScriptParser::parseNumber(char firstChar, Common::String &outToken) {
+ char ch = 0;
+
+ if (firstChar == '0') {
+ bool mightBeOctal = true;
+
+ if (readChar(ch)) {
+ if (ch == 'x' || ch == 'X') {
+ char prefix[2] = {firstChar, ch};
+ Common::String hexDigits;
+ if (!parseHexDigits(hexDigits))
+ return false;
+
+ outToken = Common::String(prefix, 2) + hexDigits;
+ return true;
+ } else if (ch == '.' || ch == 'e' || ch == 'E') {
+ mightBeOctal = false;
+ requeueChar(ch);
+ } else
+ requeueChar(ch);
+ }
+
+ if (mightBeOctal) {
+ Common::String octalDigits;
+ if (!parseOctalDigits(octalDigits))
+ return false;
+
+ outToken = Common::String('0') + octalDigits;
+ return true;
+ }
+ }
+
+ outToken = Common::String(firstChar);
+
+ // Decimal number
+ for (;;) {
+ if (!readChar(ch))
+ return true;
+
+ if (ch >= '0' && ch <= '9') {
+ outToken += ch;
+ continue;
+ }
+
+ if (ch == '.') {
+ outToken += ch;
+
+ Common::String fractionalPart;
+ if (!parseFloatFractionalPart(fractionalPart))
+ return false;
+
+ outToken += fractionalPart;
+ return true;
+ }
+
+ if (ch == 'e' || ch == 'E') {
+ outToken += ch;
+
+ Common::String exponentPart;
+ if (!parseFloatExponentPart(exponentPart))
+ return false;
+
+ outToken += exponentPart;
+ return true;
+ }
+
+ if (isAlpha(ch)) {
+ warning("Invalid floating point constantin boot script");
+ return false;
+ }
+
+ requeueChar(ch);
+ return true;
+ }
+}
+
+bool BootScriptParser::parseIdentifier(Common::String &outToken) {
+ outToken.clear();
+
+ char ch = 0;
+ while (readChar(ch)) {
+ if (isIdentifierChar(ch))
+ outToken += ch;
+ else {
+ requeueChar(ch);
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool BootScriptParser::parseQuotedString(char quoteChar, Common::String &outToken) {
+ outToken = Common::String(quoteChar);
+
+ char ch = 0;
+ while (readChar(ch)) {
+ if (ch == '\r' || ch == '\n')
+ break;
+
+ outToken += ch;
+
+ if (ch == '\\') {
+ if (!readChar(ch))
+ break;
+
+ outToken += ch;
+ } else if (ch == quoteChar)
+ return true;
+ }
+
+ error("Unterminated quoted string/char in boot script");
+
+ return false;
+}
+
+bool BootScriptParser::parseFloatFractionalPart(Common::String &outToken) {
+ for (;;) {
+ char ch = 0;
+ if (!readChar(ch))
+ return true;
+
+ if (ch == 'e' || ch == 'E') {
+ outToken += ch;
+
+ Common::String expPart;
+ if (!parseFloatExponentPart(expPart))
+ return false;
+
+ outToken += expPart;
+ return true;
+ }
+
+ if (isDigit(ch))
+ outToken += ch;
+ else if (ch == 'f' || ch == 'F') {
+ outToken += ch;
+ if (!checkFloatSuffix())
+ return false;
+
+ return true;
+ } else if (isAlpha(ch)) {
+ error("Invalid characters in floating point constant");
+ } else
+ return true;
+ }
+}
+
+bool BootScriptParser::parseFloatExponentPart(Common::String &outToken) {
+ char ch = 0;
+
+ if (!readChar(ch)) {
+ error("Missing digit sequence in floating point constant");
+ return false;
+ }
+
+ if (ch == '-' || ch == '+') {
+ outToken += ch;
+ if (!readChar(ch)) {
+ error("Missing digit sequence in floating point constant");
+ return false;
+ }
+ }
+
+ if (!isDigit(ch)) {
+ error("Missing digit sequence in floating point constant");
+ return false;
+ }
+
+ for (;;) {
+ if (isDigit(ch))
+ outToken += ch;
+ else if (ch == 'f' || ch == 'F') {
+ outToken += ch;
+ if (!checkFloatSuffix())
+ return false;
+
+ return true;
+ } else if (isAlpha(ch)) {
+ error("Invalid characters in floating point constant");
+ return false;
+ } else
+ return true;
+ }
+}
+
+bool BootScriptParser::parseHexDigits(Common::String &outToken) {
+ char ch = 0;
+
+ if (!readChar(ch)) {
+ error("Missing hex digits in boot script constant");
+ return false;
+ }
+
+ for (;;) {
+ if (isDigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'))
+ outToken += ch;
+ else if (isAlpha(ch)) {
+ error("Invalid characters in hex constant");
+ return false;
+ } else
+ return true;
+ }
+}
+
+bool BootScriptParser::parseOctalDigits(Common::String &outToken) {
+ char ch = 0;
+
+ for (;;) {
+ if (ch >= '0' && ch <= '7')
+ outToken += ch;
+ else if (isAlpha(ch) || isDigit(ch)) {
+ error("Invalid characters in octal constant");
+ return false;
+ } else
+ return true;
+ }
+}
+
+bool BootScriptParser::checkFloatSuffix() {
+ char ch = 0;
+
+ if (readChar(ch)) {
+ if (isIdentifierChar(ch)) {
+ error("Invalid characters after floating point suffix");
+ return false;
+ }
+
+ requeueChar(ch);
+ }
+
+ return true;
+}
+
+bool BootScriptParser::isIdentifierInitialChar(char c) {
+ return isAlpha(c) || (c == '_');
+}
+
+bool BootScriptParser::isIdentifierChar(char c) {
+ return isDigit(c) || isIdentifierInitialChar(c);
+}
+
+bool BootScriptParser::isDigit(char c) {
+ return c >= '0' && c <= '9';
+}
+
+bool BootScriptParser::isAlpha(char c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+}
+
+uint BootScriptParser::evalOctalIntegral(const Common::String &token) {
+ uint result = 0;
+ uint maxBeforeMul = std::numeric_limits<uint>::max() / 8;
+
+ for (uint i = 0; i < token.size(); i++) {
+ if (result > maxBeforeMul)
+ error("Integer overflow evaluating octal value %s", token.c_str());
+
+ char c = token[i];
+ if (c >= '0' && c <= '7')
+ result = result * 8u + static_cast<uint>(c - '0');
+ else
+ error("Invalid character in octal constant %s", token.c_str());
}
- if (gameDesc.desc.platform == Common::kPlatformMacintosh) {
- Common::Array<Boot::FileIdentification> macFiles;
+ return result;
+}
+
+uint BootScriptParser::evalDecimalIntegral(const Common::String &token) {
+ uint result = 0;
+ uint maxBeforeMul = std::numeric_limits<uint>::max() / 10;
+
+ for (uint i = 0; i < token.size(); i++) {
+ if (result > maxBeforeMul)
+ error("Integer overflow evaluating octal value %s", token.c_str());
+
+ char c = token[i];
+ if (c >= '0' && c <= '9')
+ result = result * 10u + static_cast<uint>(c - '0');
+ else
+ error("Invalid character in octal constant %s", token.c_str());
+ }
+
+ return result;
+}
+
+uint BootScriptParser::evalHexIntegral(const Common::String &token) {
+ uint result = 0;
+ uint maxBeforeMul = std::numeric_limits<uint>::max() / 16;
+
+ for (uint i = 2; i < token.size(); i++) {
+ if (result > maxBeforeMul)
+ error("Integer overflow evaluating octal value %s", token.c_str());
+
+ char c = token[i];
+ if (c >= '0' && c <= '9')
+ result = result * 16u + static_cast<uint>(c - '0');
+ else if (c >= 'a' && c <= 'f')
+ result = result * 16u + static_cast<uint>(c - 'a' + 0xa);
+ else if (c >= 'A' && c <= 'F')
+ result = result * 16u + static_cast<uint>(c - 'A' + 0xA);
+ else
+ error("Invalid character in hex constant %s", token.c_str());
+ }
+
+ return result;
+}
+
+char BootScriptParser::evalEscapeSequence(const Common::String &token, uint startPos, uint maxEndPos, uint &outLength) {
+ if (startPos == maxEndPos)
+ error("Unexpectedly terminated escape sequence in token %s", token.c_str());
+
+ char firstEscapeChar = token[startPos];
+
+ if (firstEscapeChar == 'x') {
+ uint hexLength = 0;
+ char c = evalHexEscapeSequence(token, startPos + 1, maxEndPos, hexLength);
+ outLength = hexLength + 1;
+ return c;
+ }
+
+ if (firstEscapeChar >= '0' && firstEscapeChar <= '7')
+ return evalOctalEscapeSequence(token, startPos, maxEndPos, outLength);
+
+ if (firstEscapeChar == '\'')
+ return '\'';
+ if (firstEscapeChar == '\"')
+ return '\"';
+ if (firstEscapeChar == '\?')
+ return '\?';
+ if (firstEscapeChar == '\\')
+ return '\\';
+ if (firstEscapeChar == '\a')
+ return '\a';
+ if (firstEscapeChar == '\b')
+ return '\b';
+ if (firstEscapeChar == '\f')
+ return '\f';
+ if (firstEscapeChar == '\n')
+ return '\n';
+ if (firstEscapeChar == '\r')
+ return '\r';
+ if (firstEscapeChar == '\t')
+ return '\t';
+ if (firstEscapeChar == '\v')
+ return '\v';
+
+ error("Unknown escape character in %s", token.c_str());
+ return '\0';
+}
+
+char BootScriptParser::evalOctalEscapeSequence(const Common::String &token, uint pos, uint maxEndPos, uint &outLength) {
+ uint length = 0;
+ uint result = 0;
+ while (length < 3 && pos < maxEndPos) {
+ char c = token[pos];
+ if (c < '0' || c > '7')
+ break;
+
+ result = result * 8u + (c - '0');
+ pos++;
+ length++;
+ }
+
+ if (result > 255)
+ error("Overflowed octal character escape in token %s", token.c_str());
+
+ outLength = length;
+ return static_cast<char>(static_cast<unsigned char>(result));
+}
+
+char BootScriptParser::evalHexEscapeSequence(const Common::String &token, uint pos, uint maxEndPos, uint &outLength) {
+ uint length = 0;
+ uint result = 0;
+ while (pos < maxEndPos) {
+ char c = token[pos];
+ if (c >= '0' && c <= '9')
+ result = result * 16u + (c - '0');
+ else if (c >= 'a' && c <= 'f')
+ result = result * 16u + (c - 'a' + 0xa);
+ else if (c >= 'A' && c <= 'F')
+ result = result * 16u + (c - 'A' + 0xA);
+
+ if (result > 255)
+ error("Overflowed octal character escape in token %s", token.c_str());
+
+ pos++;
+ length++;
+ }
+
+ outLength = length;
+ return static_cast<char>(static_cast<unsigned char>(result));
+}
+
+
+class BootScriptContext {
+public:
+ enum PlugIn {
+ kPlugInMTI,
+ kPlugInStandard,
+ kPlugInObsidian,
+ };
+
+ enum BitDepth {
+ kBitDepthAuto,
+
+ kBitDepth8,
+ kBitDepth16,
+ kBitDepth32
+ };
+
+ explicit BootScriptContext(bool isMac);
+
+ void bootObsidianRetailMacEn();
+ void bootObsidianRetailMacJp();
+ void bootObsidianGeneric();
+ void bootObsidianRetailWinDe();
+ void bootMTIRetailMac();
+ void bootMTIGeneric();
+ void bootGeneric();
+ void bootUsingBootScript();
+
+ void finalize();
+
+ const Common::Array<Common::SharedPtr<Common::Archive> > &getPersistentArchives() const;
+ const Common::Array<PlugIn> &getPlugIns() const;
+ const VirtualFileSystemLayout &getVFSLayout() const;
+ const ManifestSubtitlesDef &getSubtitlesDef() const;
+
+ BitDepth getBitDepth() const;
+ BitDepth getEnhancedBitDepth() const;
+ const Common::Point &getResolution() const;
+
+private:
+ enum ArchiveType {
+ kArchiveTypeMacVISE,
+ kArchiveTypeStuffIt,
+ kArchiveTypeInstallShieldV3,
+ };
+
+ struct EnumBinding {
+ const char *name;
+ uint value;
+ };
+
+ void addPlugIn(PlugIn plugIn);
+ void addArchive(ArchiveType archiveType, const Common::String &mountPoint, const Common::String &archivePath);
+ void addJunction(const Common::String &virtualPath, const Common::String &physicalPath);
+ void addSubtitles(const Common::String &linesFile, const Common::String &speakersFile, const Common::String &assetMappingFile, const Common::String &modifierMappingFile);
+ void addExclusion(const Common::String &virtualPath);
+ void setResolution(uint width, uint height);
+ void setBitDepth(BitDepth bitDepth);
+ void setEnhancedBitDepth(BitDepth bitDepth);
+
+ void executeFunction(const Common::String &functionName, const Common::Array<Common::String> ¶mTokens);
+
+ void checkParams(const Common::String &functionName, const Common::Array<Common::String> ¶mTokens, uint expectedCount);
+ void parseEnumSized(const Common::String &functionName, const Common::Array<Common::String> ¶mTokens, uint paramIndex, const EnumBinding *bindings, uint numBindings, uint &outValue);
+ void parseString(const Common::String &functionName, const Common::Array<Common::String> ¶mTokens, uint paramIndex, Common::String &outValue);
+ void parseUInt(const Common::String &functionName, const Common::Array<Common::String> ¶mTokens, uint paramIndex, uint &outValue);
+
+ template<uint TSize>
+ void parseEnum(const Common::String &functionName, const Common::Array<Common::String> ¶mTokens, uint paramIndex, const EnumBinding (&bindings)[TSize], uint &outValue) {
+ parseEnumSized(functionName, paramTokens, paramIndex, bindings, TSize, outValue);
+ }
+
+ VirtualFileSystemLayout _vfsLayout;
+ Common::Array<PlugIn> _plugIns;
+
+ ManifestSubtitlesDef _subtitlesDef;
+
+ Common::Array<Common::SharedPtr<Common::Archive> > _persistentArchives;
+ bool _isMac;
+ Common::Point _preferredResolution;
+ BitDepth _bitDepth;
+ BitDepth _enhancedBitDepth;
+};
+
+BootScriptContext::BootScriptContext(bool isMac) : _isMac(isMac), _preferredResolution(0, 0), _bitDepth(kBitDepthAuto), _enhancedBitDepth(kBitDepthAuto) {
+ _vfsLayout._pathSeparator = isMac ? ':' : '/';
+
+ VirtualFileSystemLayout::ArchiveJunction fsJunction;
+ fsJunction._archive = &SearchMan;
+ fsJunction._archiveName = "fs";
+
+ _vfsLayout._archiveJunctions.push_back(fsJunction);
+}
+
+void BootScriptContext::addPlugIn(PlugIn plugIn) {
+ if (Common::find(_plugIns.begin(), _plugIns.end(), plugIn) != _plugIns.end())
+ error("Duplicated plug-in");
+
+ _plugIns.push_back(plugIn);
+}
+
+void BootScriptContext::addArchive(ArchiveType archiveType, const Common::String &mountPoint, const Common::String &archivePath) {
+ for (const VirtualFileSystemLayout::ArchiveJunction &junction : _vfsLayout._archiveJunctions) {
+ Common::String prefix = junction._archiveName + _vfsLayout._pathSeparator;
+
+ if (archivePath.hasPrefixIgnoreCase(prefix)) {
+ Common::Path path(archivePath.substr(prefix.size()), _vfsLayout._pathSeparator);
+
+ Common::SeekableReadStream *stream = nullptr;
+
+ if (_isMac)
+ stream = Common::MacResManager::openFileOrDataFork(path, *junction._archive);
+ else
+ stream = junction._archive->createReadStreamForMember(path);
+
+ if (!stream)
+ error("Couldn't mount archive from path %s", archivePath.c_str());
+
+ Common::Archive *archive = nullptr;
+
+ switch (archiveType) {
+ case kArchiveTypeMacVISE:
+ archive = Common::createMacVISEArchive(stream);
+ break;
+ case kArchiveTypeInstallShieldV3: {
+ Common::InstallShieldV3 *isa = new Common::InstallShieldV3();
+ if (isa->open(stream))
+ archive = isa;
+ }
+ break;
+ case kArchiveTypeStuffIt:
+ archive = Common::createStuffItArchive(stream, false);
+ break;
+ default:
+ error("Unknown archive type");
+ }
+
+ if (!archive)
+ error("Couldn't open archive %s", archivePath.c_str());
+
+ _persistentArchives.push_back(Common::SharedPtr<Common::Archive>(archive));
+
+ VirtualFileSystemLayout::ArchiveJunction newJunction;
+ newJunction._archive = archive;
+ newJunction._archiveName = mountPoint;
+
+ _vfsLayout._archiveJunctions.push_back(newJunction);
+
+ break;
+ }
+ }
+}
+
+void BootScriptContext::addJunction(const Common::String &virtualPath, const Common::String &physicalPath) {
+ VirtualFileSystemLayout::PathJunction pathJunction;
+ pathJunction._srcPath = (virtualPath.size() == 0) ? "workspace" : (Common::String(_isMac ? "workspace:" : "workspace/") + virtualPath);
+ pathJunction._destPath = physicalPath;
+
+ _vfsLayout._pathJunctions.push_back(pathJunction);
+}
+
+void BootScriptContext::addSubtitles(const Common::String &linesFile, const Common::String &speakersFile, const Common::String &assetMappingFile, const Common::String &modifierMappingFile) {
+ _subtitlesDef.linesTablePath = linesFile;
+ _subtitlesDef.assetMappingTablePath = assetMappingFile;
+ _subtitlesDef.speakerTablePath = speakersFile;
+ _subtitlesDef.modifierMappingTablePath = modifierMappingFile;
+}
+
+void BootScriptContext::addExclusion(const Common::String &virtualPath) {
+ _vfsLayout._exclusions.push_back(virtualPath);
+}
+
+void BootScriptContext::setResolution(uint width, uint height) {
+ _preferredResolution = Common::Point(width, height);
+}
+
+void BootScriptContext::setBitDepth(BitDepth bitDepth) {
+ _bitDepth = bitDepth;
+}
+
+void BootScriptContext::setEnhancedBitDepth(BitDepth bitDepth) {
+ _enhancedBitDepth = bitDepth;
+}
+
+void BootScriptContext::bootObsidianRetailMacEn() {
+ addPlugIn(kPlugInObsidian);
+ addPlugIn(kPlugInStandard);
+
+ addArchive(kArchiveTypeStuffIt, "installer", "fs:Obsidian Installer");
+
+ addJunction("", "installer:Obsidian \xc4");
+ addJunction("Obsidian Data 1", "installer:Obsidian Data 1");
+ addJunction("Obsidian Data 2", "fs:Obsidian Data 2");
+ addJunction("Obsidian Data 3", "fs:Obsidian Data 3");
+ addJunction("Obsidian Data 4", "fs:Obsidian Data 4");
+ addJunction("Obsidian Data 5", "fs:Obsidian Data 5");
+ addJunction("Obsidian Data 6", "fs:Obsidian Data 6");
+
+ addExclusion("workspace:Obsidian Data 0");
+
+ addSubtitles("subtitles_lines_obsidian_en.csv", "subtitles_speakers_obsidian_en.csv", "subtitles_asset_mapping_obsidian_en.csv", "subtitles_modifier_mapping_obsidian_en.csv");
+}
+
+void BootScriptContext::bootObsidianRetailMacJp() {
+ addPlugIn(kPlugInObsidian);
+ addPlugIn(kPlugInStandard);
+
+ addArchive(kArchiveTypeStuffIt, "installer", "fs:xn--u9j9ecg0a2fsa1io6k6jkdc2k");
+
+ addJunction("workspace", "installer");
+ addJunction("workspace:Obsidian Data 2", "fs:Obsidian Data 2");
+ addJunction("workspace:Obsidian Data 3", "fs:Obsidian Data 3");
+ addJunction("workspace:Obsidian Data 4", "fs:Obsidian Data 4");
+ addJunction("workspace:Obsidian Data 5", "fs:Obsidian Data 5");
+ addJunction("workspace:Obsidian Data 6", "fs:Obsidian Data 6");
+
+ addExclusion("workspace:Obsidian Data 0");
+}
+
+void BootScriptContext::bootObsidianGeneric() {
+ addPlugIn(kPlugInObsidian);
+ addPlugIn(kPlugInStandard);
+
+ addSubtitles("subtitles_lines_obsidian_en.csv", "subtitles_speakers_obsidian_en.csv", "subtitles_asset_mapping_obsidian_en.csv", "subtitles_modifier_mapping_obsidian_en.csv");
+}
+
+void BootScriptContext::bootObsidianRetailWinDe() {
+ addPlugIn(kPlugInObsidian);
+ addPlugIn(kPlugInStandard);
+
+ addArchive(kArchiveTypeInstallShieldV3, "installer", "_SETUP.1");
+
+ addJunction("workspace/Obsidian.exe", "installer/Group1/Obsidian.exe");
+ addJunction("workspace/Resource/Obsidian.c95", "installer/Group2/Obsidian.c95");
+ addJunction("workspace/Resource/MCURSORS.C95", "installer/Group2/MCURSORS.C95");
+
+ addJunction("workspace", "fs");
+}
+
+void BootScriptContext::bootMTIRetailMac() {
+ addPlugIn(kPlugInMTI);
+ addPlugIn(kPlugInStandard);
+
+ addJunction("workspace:mPlayer PPC", "fs:MPlayer PPC");
+ addJunction("workspace:mPlayer PPC:Resource", "fs:MPlayer PPC:Resource");
+ addJunction("workspace:MTI1", "fs:xn--MTI1-8b7a");
+ addJunction("workspace:MTI2", "fs:MTI2");
+ addJunction("workspace:MTI3", "fs:MTI3");
+ addJunction("workspace:MTI4", "fs:MTI4");
+
+ addJunction("workspace:VIDEO", "fs:VIDEO");
+}
+
+void BootScriptContext::bootMTIGeneric() {
+ addPlugIn(kPlugInMTI);
+ addPlugIn(kPlugInStandard);
+}
+
+void BootScriptContext::bootGeneric() {
+ addPlugIn(kPlugInStandard);
+}
+
+void BootScriptContext::bootUsingBootScript() {
+ const char *bootFileName = _isMac ? MTROPOLIS_MAC_BOOT_SCRIPT_NAME : MTROPOLIS_WIN_BOOT_SCRIPT_NAME;
+
+ Common::File f;
+ if (!f.open(bootFileName))
+ error("Couldn't open boot script '%s'", bootFileName);
+
+ BootScriptParser parser(f);
+
+ Common::String functionName;
+ while (parser.readToken(functionName)) {
+ parser.expect("(");
+
+ Common::Array<Common::String> paramTokens;
+
+ {
+ Common::String paramToken;
+ if (!parser.readToken(paramToken))
+ error("Unexpected EOF or error when reading parameter token");
+
+ if (paramToken != ")") {
+ paramTokens.push_back(paramToken);
+
+ for (;;) {
+ if (!parser.readToken(paramToken))
+ error("Unexpected EOF or error when reading parameter token");
+
+ if (paramToken == ")")
+ break;
+
+ if (paramToken != ",")
+ error("Unexpected token %s while reading parameter list", paramToken.c_str());
+
+ if (!parser.readToken(paramToken))
+ error("Unexpected EOF or error when reading parameter token");
+
+ paramTokens.push_back(paramToken);
+ }
+ }
+ }
+
+ parser.expect(";");
+
+ executeFunction(functionName, paramTokens);
+ }
+}
+
+#define ENUM_BINDING(name) \
+ { #name, name }
+
+void BootScriptContext::executeFunction(const Common::String &functionName, const Common::Array<Common::String> ¶mTokens) {
+ const EnumBinding plugInEnum[] = {ENUM_BINDING(kPlugInMTI),
+ ENUM_BINDING(kPlugInStandard),
+ ENUM_BINDING(kPlugInObsidian)};
+
+ const EnumBinding bitDepthEnum[] = {ENUM_BINDING(kBitDepthAuto),
+ ENUM_BINDING(kBitDepth8),
+ ENUM_BINDING(kBitDepth16),
+ ENUM_BINDING(kBitDepth32)};
+
+ const EnumBinding archiveTypeEnum[] = {ENUM_BINDING(kArchiveTypeMacVISE),
+ ENUM_BINDING(kArchiveTypeStuffIt),
+ ENUM_BINDING(kArchiveTypeInstallShieldV3)};
+
+
+ Common::String str1, str2, str3, str4;
+ uint ui1 = 0;
+ uint ui2 = 0;
+
+ if (functionName == "addPlugIn") {
+ checkParams(functionName, paramTokens, 1);
+ parseEnum(functionName, paramTokens, 0, plugInEnum, ui1);
+
+ addPlugIn(static_cast<PlugIn>(ui1));
+ } else if (functionName == "addArchive") {
+ checkParams(functionName, paramTokens, 3);
+ parseEnum(functionName, paramTokens, 0, archiveTypeEnum, ui1);
+ parseString(functionName, paramTokens, 1, str1);
+ parseString(functionName, paramTokens, 2, str2);
+
+ addArchive(static_cast<ArchiveType>(ui1), str1, str2);
+ } else if (functionName == "addJunction") {
+ checkParams(functionName, paramTokens, 2);
+ parseString(functionName, paramTokens, 0, str1);
+ parseString(functionName, paramTokens, 1, str2);
+
+ addJunction(str1, str2);
+ } else if (functionName == "addSubtitles") {
+ checkParams(functionName, paramTokens, 4);
+ parseString(functionName, paramTokens, 0, str1);
+ parseString(functionName, paramTokens, 1, str2);
+ parseString(functionName, paramTokens, 2, str3);
+ parseString(functionName, paramTokens, 3, str4);
+
+ addSubtitles(str1, str2, str3, str4);
+ } else if (functionName == "addExclusion") {
+ checkParams(functionName, paramTokens, 1);
+ parseString(functionName, paramTokens, 0, str1);
+
+ addExclusion(str1);
+ } else if (functionName == "setResolution") {
+ checkParams(functionName, paramTokens, 2);
+ parseUInt(functionName, paramTokens, 0, ui1);
+ parseUInt(functionName, paramTokens, 1, ui2);
+
+ setResolution(ui1, ui2);
+ } else if (functionName == "setBitDepth") {
+ checkParams(functionName, paramTokens, 1);
+ parseEnum(functionName, paramTokens, 0, bitDepthEnum, ui1);
+
+ setBitDepth(static_cast<BitDepth>(ui1));
+ } else if (functionName == "setEnhancedBitDepth") {
+ checkParams(functionName, paramTokens, 1);
+ parseEnum(functionName, paramTokens, 0, bitDepthEnum, ui1);
+
+ setEnhancedBitDepth(static_cast<BitDepth>(ui1));
+ } else {
+ error("Unknown function '%s'", functionName.c_str());
+ }
+}
+
+#undef ENUM_BINDING
+
+void BootScriptContext::checkParams(const Common::String &functionName, const Common::Array<Common::String> ¶mTokens, uint expectedCount) {
+ if (expectedCount != paramTokens.size())
+ error("Expected %u parameters for function %s", paramTokens.size(), functionName.c_str());
+}
+
+void BootScriptContext::parseEnumSized(const Common::String &functionName, const Common::Array<Common::String> ¶mTokens, uint paramIndex, const EnumBinding *bindings, uint numBindings, uint &outValue) {
+ const Common::String ¶m = paramTokens[paramIndex];
+
+ if (BootScriptParser::classifyToken(param) != BootScriptParser::kTokenTypeIdentifier)
+ error("Expected identifier for parameter %u of function %s", paramIndex, functionName.c_str());
+
+ for (uint i = 0; i < numBindings; i++) {
+ if (param == bindings[i].name) {
+ outValue = bindings[i].value;
+ return;
+ }
+ }
+
+ error("Couldn't resolve enum value %s for parameter %u of function %s", param.c_str(), paramIndex, functionName.c_str());
+}
+
+void BootScriptContext::parseString(const Common::String &functionName, const Common::Array<Common::String> ¶mTokens, uint paramIndex, Common::String &outValue) {
+ const Common::String ¶m = paramTokens[paramIndex];
+
+ if (BootScriptParser::classifyToken(param) != BootScriptParser::kTokenTypeString)
+ error("Expected string for parameter %u of function %s", paramIndex, functionName.c_str());
+
+ outValue = BootScriptParser::evalString(param);
+}
+
+void BootScriptContext::parseUInt(const Common::String &functionName, const Common::Array<Common::String> ¶mTokens, uint paramIndex, uint &outValue) {
+ const Common::String ¶m = paramTokens[paramIndex];
+
+ BootScriptParser::TokenType tt = BootScriptParser::classifyToken(param);
+
+ if (tt != BootScriptParser::kTokenTypeDecimalConstant && tt != BootScriptParser::kTokenTypeOctalConstant && tt != BootScriptParser::kTokenTypeHexConstant)
+ error("Expected integral constant for parameter %u of function %s", paramIndex, functionName.c_str());
+
+ outValue = BootScriptParser::evalIntegral(param);
+}
+
+void BootScriptContext::finalize() {
+ if (_vfsLayout._pathJunctions.size() == 0) {
+ VirtualFileSystemLayout::PathJunction pathJunction;
+ pathJunction._srcPath = "workspace";
+ pathJunction._destPath = "fs";
+
+ _vfsLayout._pathJunctions.push_back(pathJunction);
+ }
+}
+
+const Common::Array<Common::SharedPtr<Common::Archive> > &BootScriptContext::getPersistentArchives() const {
+ return _persistentArchives;
+}
+
+const Common::Array<BootScriptContext::PlugIn> &BootScriptContext::getPlugIns() const {
+ return _plugIns;
+}
+
+const VirtualFileSystemLayout &BootScriptContext::getVFSLayout() const {
+ return _vfsLayout;
+}
+
+const ManifestSubtitlesDef &BootScriptContext::getSubtitlesDef() const {
+ return _subtitlesDef;
+}
+
+BootScriptContext::BitDepth BootScriptContext::getBitDepth() const {
+ return _bitDepth;
+}
+
+BootScriptContext::BitDepth BootScriptContext::getEnhancedBitDepth() const {
+ return _enhancedBitDepth;
+}
+
+const Common::Point &BootScriptContext::getResolution() const {
+ return _preferredResolution;
+}
+
+namespace Games {
+
+
+
+const Game games[] = {
+ // Boot script
+ {
+ MTBOOT_USE_BOOT_SCRIPT,
+ &BootScriptContext::bootUsingBootScript
+ },
+
+ // Obsidian - Retail - Macintosh - English
+ {
+ MTBOOT_OBSIDIAN_RETAIL_MAC_EN,
+ &BootScriptContext::bootObsidianRetailMacEn
+ },
+ // Obsidian - Retail - Macintosh - Japanese
+ {
+ MTBOOT_OBSIDIAN_RETAIL_MAC_JP,
+ &BootScriptContext::bootObsidianRetailMacJp
+ },
+ // Obsidian - Retail - Windows - English
+ {
+ MTBOOT_OBSIDIAN_RETAIL_WIN_EN,
+ &BootScriptContext::bootObsidianGeneric
+ },
+ // Obsidian - Retail - Windows - German - Installed
+ {
+ MTBOOT_OBSIDIAN_RETAIL_WIN_DE_INSTALLED,
+ &BootScriptContext::bootObsidianGeneric
+ },
+ // Obsidian - Retail - Windows - German - Disc
+ {
+ MTBOOT_OBSIDIAN_RETAIL_WIN_DE_DISC,
+ &BootScriptContext::bootObsidianRetailWinDe
+ },
+ // Obsidian - Retail - Windows - Italian
+ {
+ MTBOOT_OBSIDIAN_RETAIL_WIN_IT,
+ &BootScriptContext::bootObsidianGeneric
+ },
+ // Obsidian - Demo - Macintosh - English
+ {
+ MTBOOT_OBSIDIAN_DEMO_MAC_EN,
+ &BootScriptContext::bootObsidianGeneric
+ },
+ // Obsidian - Demo - Windows - English - Variant 1
+ {
+ MTBOOT_OBSIDIAN_DEMO_WIN_EN_1,
+ &BootScriptContext::bootObsidianGeneric
+ },
+ // Obsidian - Demo - Windows - English - Variant 2
+ {
+ MTBOOT_OBSIDIAN_DEMO_WIN_EN_2,
+ &BootScriptContext::bootObsidianGeneric
+ },
+ // Obsidian - Demo - Windows - English - Variant 3
+ {
+ MTBOOT_OBSIDIAN_DEMO_WIN_EN_3,
+ &BootScriptContext::bootObsidianGeneric
+ },
+ // Obsidian - Demo - Windows - English - Variant 4
+ {
+ MTBOOT_OBSIDIAN_DEMO_WIN_EN_4,
+ &BootScriptContext::bootObsidianGeneric
+ },
+ // Obsidian - Demo - Windows - English - Variant 5
+ {
+ MTBOOT_OBSIDIAN_DEMO_WIN_EN_5,
+ &BootScriptContext::bootObsidianGeneric
+ },
+ // Obsidian - Demo - Windows - English - Variant 6
+ {
+ MTBOOT_OBSIDIAN_DEMO_WIN_EN_6,
+ &BootScriptContext::bootObsidianGeneric
+ },
+ // Obsidian - Demo - Windows - English - Variant 7
+ {
+ MTBOOT_OBSIDIAN_DEMO_WIN_EN_7,
+ &BootScriptContext::bootObsidianGeneric
+ },
+ // Muppet Treasure Island - Retail - Macintosh - Multiple languages
+ {
+ MTBOOT_MTI_RETAIL_MAC,
+ &BootScriptContext::bootMTIRetailMac
+ },
+ // Muppet Treasure Island - Retail - Windows - Multiple languages
+ {
+ MTBOOT_MTI_RETAIL_WIN,
+ &BootScriptContext::bootMTIGeneric
+ },
+ // Muppet Treasure Island - Demo - Windows
+ {
+ MTBOOT_MTI_DEMO_WIN,
+ &BootScriptContext::bootMTIGeneric
+ },
+ // Uncle Albert's Magical Album - German - Windows
+ {
+ MTBOOT_ALBERT1_WIN_DE,
+ &BootScriptContext::bootGeneric
+ },
+ // Uncle Albert's Fabulous Voyage - German - Windows
+ {
+ MTBOOT_ALBERT2_WIN_DE,
+ &BootScriptContext::bootGeneric
+ },
+ // Uncle Albert's Mysterious Island - German - Windows
+ {
+ MTBOOT_ALBERT3_WIN_DE,
+ &BootScriptContext::bootGeneric
+ },
+ // SPQR: The Empire's Darkest Hour - Retail - Windows - English
+ {
+ MTBOOT_SPQR_RETAIL_WIN,
+ &BootScriptContext::bootGeneric
+ },
+ // SPQR: The Empire's Darkest Hour - Retail - Macintosh - English
+ {
+ MTBOOT_SPQR_RETAIL_MAC,
+ &BootScriptContext::bootGeneric
+ },
+ // Star Trek: The Game Show - Demo - Windows
+ {
+ MTBOOT_STTGS_DEMO_WIN,
+ &BootScriptContext::bootGeneric
+ },
+ // Unit: Rebooted
+ {
+ MTBOOT_UNIT_REBOOTED_WIN,
+ &BootScriptContext::bootGeneric
+ },
+};
+
+} // End of namespace Games
+
+Common::SharedPtr<MTropolis::PlugIn> loadStandardPlugIn(const MTropolisGameDescription &gameDesc) {
+ Common::SharedPtr<MTropolis::PlugIn> standardPlugIn = PlugIns::createStandard();
+ static_cast<Standard::StandardPlugIn *>(standardPlugIn.get())->getHacks().allowGarbledListModData = true;
+ return standardPlugIn;
+}
+
+Common::SharedPtr<MTropolis::PlugIn> loadObsidianPlugIn(const MTropolisGameDescription &gameDesc, Common::Archive &fs, const Common::Path &pluginsLocation) {
+ bool isMac = (gameDesc.desc.platform == Common::kPlatformMacintosh);
+ bool isRetail = ((gameDesc.desc.flags & ADGF_DEMO) == 0);
+ bool isEnglish = (gameDesc.desc.language == Common::EN_ANY);
+
+ return ObsidianGameDataHandler::loadPlugIn(fs, pluginsLocation, isMac, isRetail, isEnglish);
+}
+
+Common::SharedPtr<MTropolis::PlugIn> loadMTIPlugIn(const MTropolisGameDescription &gameDesc) {
+ Common::SharedPtr<MTropolis::PlugIn> mtiPlugIn(PlugIns::createMTI());
+ return mtiPlugIn;
+}
- debug(1, "Attempting to boot Macintosh game...");
+enum PlayerType {
+ kPlayerTypeNone,
- const Boot::ManifestFile *fileDesc = bootGame->manifest;
- while (fileDesc->fileName) {
- const char *fileName = fileDesc->fileName;
+ kPlayerTypeWin16,
+ kPlayerTypeWin32,
- Boot::FileIdentification ident;
- ident.fileName = fileName;
- ident.category = static_cast<Boot::ManifestFileType>(fileDesc->fileType);
- ident.macType.value = 0;
- ident.macCreator.value = 0;
- if (ident.category == Boot::MTFT_AUTO && !Boot::getMacTypesForFile(fileName, ident.macType.value, ident.macCreator.value))
- error("Couldn't determine Mac file type code for file '%s'", fileName);
+ kPlayerTypeMac68k,
+ kPlayerTypeMacPPC,
+ kPlayerTypeMacFatBinary,
+};
- macFiles.push_back(ident);
+PlayerType evaluateWinPlayer(Common::ArchiveMember &archiveMember, bool mustBePE) {
+ Common::SharedPtr<Common::SeekableReadStream> stream(archiveMember.createReadStream());
- fileDesc++;
+ if (!stream)
+ return kPlayerTypeNone;
+
+ // Largest known mPlayer executable is slightly over 1MB
+ // Smallest known mPlayer executable is 542kb
+ // By excluding ~2MB files we also ignore QT32.EXE QuickTime installers
+ if (stream->size() < 512 * 1024 || stream->size() > 3 * 1024 * 1024 / 2)
+ return kPlayerTypeNone;
+
+ if (!stream->seek(0x3c))
+ return kPlayerTypeNone;
+
+ uint32 peOffset = stream->readUint32LE();
+ if (stream->eos() || stream->err())
+ return kPlayerTypeNone;
+
+ bool isPE = false;
+ if (stream->size() - 4 >= peOffset) {
+ if (stream->seek(peOffset)) {
+ uint32 possiblePEHeader = stream->readUint32LE();
+ if (!stream->eos() && !stream->err() && possiblePEHeader == 0x00004550)
+ isPE = true;
}
+ }
- 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; // Unused
- bool haveAnyMFxm = false;
-
- for (Boot::FileIdentification &macFile : macFiles) {
- if (macFile.category == Boot::MTFT_AUTO) {
- switch (macFile.macType.value) {
- 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; // Unused
+ stream->clearErr();
+
+ // If we already found a Win32 player, ignore Win16 players
+ if (mustBePE && !isPE)
+ return kPlayerTypeNone;
+
+ const char *signature = "mTropolis Windows Player";
+ uint signatureLength = strlen(signature);
+
+ if (!stream->seek(0))
+ return kPlayerTypeNone;
+
+ Common::Array<char> fileContents;
+ fileContents.resize(stream->size());
+
+ if (stream->read(&fileContents[0], fileContents.size()) != fileContents.size())
+ return kPlayerTypeNone;
+
+ stream.reset();
+
+ // Look for signature
+ uint lastStartPos = fileContents.size() - signatureLength * (isPE ? 2 : 1);
+
+ for (uint i = 0; i < lastStartPos; i++) {
+ bool isMatch = true;
+
+ for (uint j = 0; j < signatureLength; j++) {
+ if (isPE) {
+ if (fileContents[i + j * 2] != signature[j] || fileContents[i + j * 2 + 1] != '\0') {
+ isMatch = false;
break;
- default:
+ }
+ } else {
+ if (fileContents[i + j] != signature[j]) {
+ isMatch = false;
break;
- };
+ }
}
}
- bool isMT2CrossPlatform = (haveAnyMFmx && !haveAnyMFmm);
- if (isMT2CrossPlatform && haveAnyMFxm)
- error("Unexpected combination of player file types");
+ if (isMatch)
+ return isPE ? kPlayerTypeWin32 : kPlayerTypeWin16;
+ }
- // Identify unknown files
- for (Boot::FileIdentification &macFile : macFiles) {
- if (macFile.category == Boot::MTFT_AUTO) {
- switch (macFile.macType.value) {
- case MKTAG('M', 'F', 'm', 'm'):
- macFile.category = Boot::MTFT_MAIN;
- break;
- case MKTAG('M', 'F', 'm', 'x'):
- macFile.category = isMT2CrossPlatform ? Boot::MTFT_MAIN : Boot::MTFT_ADDITIONAL;
- break;
- case MKTAG('M', 'F', 'x', 'm'):
- case MKTAG('M', 'F', 'x', 'x'):
- macFile.category = Boot::MTFT_ADDITIONAL;
- break;
- case MKTAG('A', 'P', 'P', 'L'):
- macFile.category = Boot::MTFT_PLAYER;
- break;
- case MKTAG('M', 'F', 'c', 'o'):
- case MKTAG('M', 'F', 'c', 'r'):
- case MKTAG('M', 'F', 'X', 'O'):
- macFile.category = Boot::MTFT_EXTENSION;
- break;
- default:
- error("Failed to categorize input file '%s'", macFile.fileName.c_str());
- break;
- };
- }
+ return kPlayerTypeNone;
+}
+
+void findWindowsPlayer(Common::Archive &fs, Common::Path &resolvedPath, PlayerType &resolvedPlayerType) {
+ Common::ArchiveMemberList executableFiles;
+
+ fs.listMatchingMembers(executableFiles, "*.exe", true);
+
+ if (executableFiles.size() == 0)
+ error("No executable files were found");
+
+ Common::ArchiveMemberPtr bestPlayer;
+ PlayerType bestPlayerType = kPlayerTypeNone;
+ uint numPlayersInCategory = 0;
+
+ for (const Common::ArchiveMemberPtr &archiveMember : executableFiles) {
+ PlayerType playerType = evaluateWinPlayer(*archiveMember, bestPlayerType == kPlayerTypeWin32);
+
+ debug(1, "Evaluated possible player executable %s as quality %i", archiveMember->getPathInArchive().toString(fs.getPathSeparator()).c_str(), static_cast<int>(playerType));
+
+ if (playerType > bestPlayerType) {
+ bestPlayerType = playerType;
+ bestPlayer = archiveMember;
+ numPlayersInCategory = 1;
+ } else if (playerType == bestPlayerType)
+ numPlayersInCategory++;
+ }
+
+ if (numPlayersInCategory == 0)
+ error("Couldn't find any mTropolis Player executables");
+
+ if (numPlayersInCategory != 1)
+ error("Found multiple mTropolis Player executables of the same quality");
+
+ resolvedPath = bestPlayer->getPathInArchive();
+ resolvedPlayerType = bestPlayerType;
+}
+
+PlayerType evaluateMacPlayer(Common::Archive &fs, Common::ArchiveMember &archiveMember) {
+ Common::Path path = archiveMember.getPathInArchive();
+
+ Common::MacFinderInfo finderInfo;
+ if (Common::MacResManager::getFileFinderInfo(path, fs, finderInfo)) {
+ if (finderInfo.type[0] != 'A' || finderInfo.type[1] != 'P' || finderInfo.type[2] != 'P' || finderInfo.type[3] != 'L')
+ return kPlayerTypeNone;
+ }
+
+ Common::MacResManager resMan;
+ if (!resMan.open(path, fs))
+ return kPlayerTypeNone;
+
+ if (!resMan.hasResFork())
+ return kPlayerTypeNone;
+
+ Common::ScopedPtr<Common::SeekableReadStream> strStream(resMan.getResource(MKTAG('S', 'T', 'R', '#'), 200));
+ if (!strStream)
+ return kPlayerTypeNone;
+
+ uint8 strInitialBytes[12];
+
+ if (strStream->size() < sizeof(strInitialBytes))
+ return kPlayerTypeNone;
+
+ if (strStream->read(strInitialBytes, sizeof(strInitialBytes)) != sizeof(strInitialBytes))
+ return kPlayerTypeNone;
+
+ if (memcmp(strInitialBytes + 2, "\x09mTropolis", 10))
+ return kPlayerTypeNone;
+
+ bool is68k = resMan.getResIDArray(MKTAG('C', 'O', 'D', 'E')).size() > 0;
+ bool isPPC = resMan.getResIDArray(MKTAG('c', 'f', 'r', 'g')).size() > 0;
+
+ if (is68k) {
+ if (isPPC)
+ return kPlayerTypeMacFatBinary;
+ else
+ return kPlayerTypeMac68k;
+ } else {
+ if (isPPC)
+ return kPlayerTypeMacPPC;
+ else
+ return kPlayerTypeNone;
+ }
+}
+
+void findMacPlayer(Common::Archive &fs, Common::Path &resolvedPath, PlayerType &resolvedPlayerType) {
+ Common::ArchiveMemberList allFiles;
+
+ fs.listMembers(allFiles);
+
+ Common::ArchiveMemberPtr bestPlayer;
+ PlayerType bestPlayerType = kPlayerTypeNone;
+ uint numPlayersInCategory = 0;
+
+ for (const Common::ArchiveMemberPtr &archiveMember : allFiles) {
+ PlayerType playerType = evaluateMacPlayer(fs, *archiveMember);
+
+ debug(1, "Evaluated possible player executable %s as quality %i", archiveMember->getPathInArchive().toString(fs.getPathSeparator()).c_str(), static_cast<int>(playerType));
+
+ if (playerType > bestPlayerType) {
+ bestPlayerType = playerType;
+ bestPlayer = archiveMember;
+ numPlayersInCategory = 1;
+ } else if (playerType == bestPlayerType)
+ numPlayersInCategory++;
+ }
+
+ if (numPlayersInCategory == 0)
+ error("Couldn't find any mTropolis Player applications");
+
+ if (numPlayersInCategory != 1)
+ error("Found multiple mTropolis Player applications of the same quality");
+
+ if (bestPlayerType == kPlayerTypeMacFatBinary)
+ bestPlayerType = kPlayerTypeMacPPC;
+
+ resolvedPath = bestPlayer->getPathInArchive();
+ resolvedPlayerType = bestPlayerType;
+}
+
+void findWindowsMainSegment(Common::Archive &fs, Common::Path &resolvedPath, bool &resolvedIsV2) {
+ Common::ArchiveMemberList allFiles;
+ Common::ArchiveMemberList filteredFiles;
+
+ fs.listMembers(allFiles);
+
+ for (const Common::ArchiveMemberPtr &archiveMember : allFiles) {
+ Common::String fileName = archiveMember->getFileName();
+ if (fileName.hasSuffixIgnoreCase(".mpl") || fileName.hasSuffixIgnoreCase(".mfw") || fileName.hasSuffixIgnoreCase(".mfx")) {
+ filteredFiles.push_back(archiveMember);
+ debug(4, "Identified possible main segment file %s", fileName.c_str());
}
+ }
- Boot::FileIdentification *mainSegmentFile = nullptr;
- Common::Array<Boot::FileIdentification *> segmentFiles;
+ allFiles.clear();
- int addlSegments = 0;
+ if (filteredFiles.size() == 0)
+ error("Couldn't find any main segment files");
- // Bin segments
- for (Boot::FileIdentification &macFile : macFiles) {
- switch (macFile.category) {
- case Boot::MTFT_PLAYER:
- // Case handled below after cursor loading
- break;
- case Boot::MTFT_EXTENSION:
- // Case handled below after cursor loading
+ if (filteredFiles.size() != 1)
+ error("Found multiple main segment files");
+
+ resolvedPath = filteredFiles.front()->getPathInArchive();
+ resolvedIsV2 = !filteredFiles.front()->getFileName().hasSuffixIgnoreCase(".mpl");
+}
+
+bool getMacFileType(Common::Archive &fs, const Common::Path &path, uint32 &outTag) {
+ Common::MacFinderInfo finderInfo;
+
+ if (!Common::MacResManager::getFileFinderInfo(path, fs, finderInfo))
+ return false;
+
+ outTag = MKTAG(finderInfo.type[0], finderInfo.type[1], finderInfo.type[2], finderInfo.type[3]);
+ return true;
+}
+
+enum SegmentSignatureType {
+ kSegmentSignatureUnknown,
+
+ kSegmentSignatureMacV1,
+ kSegmentSignatureWinV1,
+ kSegmentSignatureMacV2,
+ kSegmentSignatureWinV2,
+ kSegmentSignatureCrossV2,
+};
+
+const uint kSignatureHeaderSize = 10;
+
+SegmentSignatureType identifyStreamBySignature(byte (&header)[kSignatureHeaderSize]) {
+ const byte macV1Signature[kSignatureHeaderSize] = {0, 0, 0xaa, 0x55, 0xa5, 0xa5, 0, 0, 0, 0};
+ const byte winV1Signature[kSignatureHeaderSize] = {1, 0, 0xa5, 0xa5, 0x55, 0xaa, 0, 0, 0, 0};
+ const byte macV2Signature[kSignatureHeaderSize] = {0, 0, 0xaa, 0x55, 0xa5, 0xa5, 2, 0, 0, 0};
+ const byte winV2Signature[kSignatureHeaderSize] = {1, 0, 0xa5, 0xa5, 0x55, 0xaa, 0, 0, 0, 2};
+ const byte crossV2Signature[kSignatureHeaderSize] = {8, 0, 0xa5, 0xa5, 0x55, 0xaa, 0, 0, 0, 2};
+
+ const byte *signatures[5] = {macV1Signature, winV1Signature, macV2Signature, winV2Signature, crossV2Signature};
+
+ for (int i = 0; i < 5; i++) {
+ const byte *signature = signatures[i];
+
+ if (!memcmp(signature, header, kSignatureHeaderSize))
+ return static_cast<SegmentSignatureType>(i + kSegmentSignatureMacV1);
+ }
+
+ return kSegmentSignatureUnknown;
+}
+
+SegmentSignatureType identifyMacFileBySignature(Common::Archive &fs, const Common::Path &path) {
+ Common::ScopedPtr<Common::SeekableReadStream> stream(Common::MacResManager::openFileOrDataFork(path, fs));
+
+ if (!stream)
+ return kSegmentSignatureUnknown;
+
+ byte header[kSignatureHeaderSize];
+ if (stream->read(header, kSignatureHeaderSize) != kSignatureHeaderSize)
+ return kSegmentSignatureUnknown;
+
+ stream.reset();
+
+ return identifyStreamBySignature(header);
+}
+
+void findMacMainSegment(Common::Archive &fs, Common::Path &resolvedPath, bool &resolvedIsV2) {
+ Common::ArchiveMemberList allFiles;
+ Common::ArchiveMemberList mfmmFiles;
+ Common::ArchiveMemberList mfmxFiles;
+ Common::ArchiveMemberList mfxmFiles;
+ Common::ArchiveMemberList mfxxFiles;
+
+ Common::ArchiveMemberList filteredFiles;
+
+
+ fs.listMembers(allFiles);
+
+ bool isV2 = false;
+
+ // This is a somewhat tricky scenario because the main segment type may be either MFmx or MFmm, but MFmx
+ // is NOT the main segment for mTropolis 1.x projects. For those projects, we expect a MFmm.
+ //
+ // MT1 Mac: MFmm[+MFmx]
+ // MT2 Mac: MFmm[+MFxm]
+ // MT2 Cross: MFmx[+MFxx]
+
+ for (const Common::ArchiveMemberPtr &archiveMember : allFiles) {
+ uint32 fileTag = 0;
+ if (getMacFileType(fs, archiveMember->getPathInArchive(), fileTag)) {
+ switch (fileTag) {
+ case MKTAG('M', 'F', 'm', 'm'):
+ mfmmFiles.push_back(archiveMember);
break;
- case Boot::MTFT_MAIN:
- mainSegmentFile = &macFile;
+ case MKTAG('M', 'F', 'm', 'x'):
+ mfmxFiles.push_back(archiveMember);
break;
- case Boot::MTFT_ADDITIONAL: {
- addlSegments++;
- int segmentID = addlSegments + 1;
-
- size_t segmentIndex = static_cast<size_t>(segmentID - 1);
- while (segmentFiles.size() <= segmentIndex)
- segmentFiles.push_back(nullptr);
- segmentFiles[segmentIndex] = &macFile;
- } break;
- case Boot::MTFT_VIDEO:
+ case MKTAG('M', 'F', 'x', 'm'):
+ mfxmFiles.push_back(archiveMember);
break;
- case Boot::MTFT_SPECIAL:
+ case MKTAG('M', 'F', 'x', 'x'):
+ mfxxFiles.push_back(archiveMember);
break;
- case Boot::MTFT_AUTO:
+ default:
break;
}
}
+ }
+
+ if (mfmmFiles.size() > 0)
+ filteredFiles = mfmmFiles;
+ else if (mfxxFiles.size() > 0) {
+ filteredFiles = mfmxFiles;
+ isV2 = true;
+ } else {
+ // No MFmm files and no MFxx files, so if there are MFmx files, they could be the main segment of
+ // a mTropolis 2.x project or additional files belonging to
+ for (const Common::ArchiveMemberPtr &mfmxFile : mfmxFiles) {
+ SegmentSignatureType signatureType = identifyMacFileBySignature(fs, mfmxFile->getPathInArchive());
+
+ if (signatureType == kSegmentSignatureCrossV2) {
+ filteredFiles.push_back(mfmxFile);
+ isV2 = true;
+ }
+ }
+ }
- if (segmentFiles.size() > 0)
- segmentFiles[0] = mainSegmentFile;
- else
- segmentFiles.push_back(mainSegmentFile);
+ if (filteredFiles.size() == 0) {
+ warning("Didn't find main segment by Finder type, inspecting all files manually. This is slow, you should use a format that preserves Finder info");
- // Load cursors
- Common::SharedPtr<CursorGraphicCollection> cursorGraphics(new CursorGraphicCollection());
+ // Didn't find any file that looks like a main segment by type, need to inspect all untagged files by signature.
+ for (const Common::ArchiveMemberPtr &archiveMember : allFiles) {
+ Common::Path path = archiveMember->getPathInArchive();
+ uint32 tag = 0;
- for (Boot::FileIdentification &macFile : macFiles) {
- if (macFile.category == Boot::MTFT_PLAYER)
- Boot::loadCursorsMac(macFile, *cursorGraphics);
- }
+ if (!getMacFileType(fs, path, tag)) {
+ SegmentSignatureType signatureType = identifyMacFileBySignature(fs, archiveMember->getPathInArchive());
- for (Boot::FileIdentification &macFile : macFiles) {
- if (macFile.category == Boot::MTFT_EXTENSION)
- Boot::loadCursorsMac(macFile, *cursorGraphics);
+ if (signatureType != kSegmentSignatureUnknown) {
+ filteredFiles.push_back(archiveMember);
+
+ if (signatureType == kSegmentSignatureMacV2 || signatureType == kSegmentSignatureWinV2 || signatureType == kSegmentSignatureCrossV2)
+ isV2 = true;
+ }
+ }
}
+ }
- // Create the project description
- desc.reset(new ProjectDescription(isMT2CrossPlatform ? KProjectPlatformCrossPlatform : kProjectPlatformMacintosh));
+ allFiles.clear();
- for (Boot::FileIdentification *segmentFile : segmentFiles) {
- if (!segmentFile)
- error("Missing segment file");
+ if (filteredFiles.size() == 0)
+ error("Couldn't find any main segment files");
- Common::SharedPtr<Common::SeekableReadStream> dataFork;
+ if (filteredFiles.size() != 1) {
+ for (const Common::ArchiveMemberPtr &archiveMember : filteredFiles)
+ warning("Possible main segment file: '%s'", archiveMember->getPathInArchive().toString(fs.getPathSeparator()).c_str());
- if (segmentFile->stream)
- dataFork = segmentFile->stream;
- else {
- dataFork.reset(Common::MacResManager::openFileOrDataFork(segmentFile->fileName));
- if (!dataFork)
- error("Segment file '%s' has no data fork", segmentFile->fileName.c_str());
- }
+ error("Found multiple main segment files");
+ }
+
+ resolvedPath = filteredFiles.front()->getPathInArchive();
+ resolvedIsV2 = isV2;
+}
+
+bool sortPathFileName(const Common::Path &a, const Common::Path &b) {
+ Common::String aFileName = a.getLastComponent().toString();
+ Common::String bFileName = b.getLastComponent().toString();
+
+ return aFileName.compareToIgnoreCase(bFileName) < 0;
+}
+
+uint32 readEndian32(Common::ReadStream &stream, bool isBE) {
+ return isBE ? stream.readUint32BE() : stream.readUint32LE();
+}
+
+uint16 readEndian16(Common::ReadStream &stream, bool isBE) {
+ return isBE ? stream.readUint16BE() : stream.readUint16LE();
+}
+
+void safeResolveBitDepthAndResolutionFromPresentationSettings(Common::SeekableReadStream &mainSegmentStream, bool isMac, uint8 &outBitDepth, uint16 &outWidth, uint16 &outHeight) {
+ byte header[kSignatureHeaderSize];
+
+ if (mainSegmentStream.read(header, kSignatureHeaderSize) != kSignatureHeaderSize)
+ error("Failed to read main segment header");
+
+ SegmentSignatureType sigType = identifyStreamBySignature(header);
+
+ if (sigType == kSegmentSignatureUnknown)
+ error("Unknown main segment signature");
- persistentResources.push_back(Boot::PersistentResource<Common::SeekableReadStream>::wrap(dataFork));
+ bool isBE = (sigType == kSegmentSignatureMacV1 || sigType == kSegmentSignatureMacV2);
- desc->addSegment(0, dataFork.get());
+ Data::DataReader catReader(kSignatureHeaderSize, mainSegmentStream, isBE ? Data::kDataFormatMacintosh : Data::kDataFormatWindows);
+
+ uint32 hdrUnknown = 0;
+
+ uint32 phTypeID = 0;
+ uint16 phRevision = 0;
+ uint32 phPersistFlags = 0;
+ uint32 phSizeIncludingTag = 0;
+ uint16 phUnknown1 = 0;
+ uint32 phCatalogPosition = 0;
+
+ if (!catReader.readMultiple(hdrUnknown, phTypeID, phRevision, phPersistFlags, phSizeIncludingTag, phUnknown1, phCatalogPosition) || phTypeID != 1002 || phRevision != 0)
+ error("Failed to read project header from main segment");
+
+ if (!mainSegmentStream.seek(phCatalogPosition))
+ error("Failed to seek to catalog");
+
+ uint32 catTypeID = 0;
+ uint16 catRevision = 0;
+
+ if (!catReader.readMultiple(catTypeID, catRevision) || catTypeID != 1000 || (catRevision != 2 && catRevision != 3))
+ error("Failed to read catalog header");
+
+ uint32 catPersistFlags = 0;
+ uint32 catSizeOfStreamAndSegmentDescs = 0;
+ uint16 catNumStreams = 0;
+ uint16 catUnknown1 = 0;
+ uint16 catUnknown2 = 0;
+ uint16 catNumSegments = 0;
+
+ if (!catReader.readMultiple(catPersistFlags, catSizeOfStreamAndSegmentDescs, catNumStreams, catUnknown1, catUnknown2, catNumSegments))
+ error("Failed to read stream descs from catalog header");
+
+ uint32 bootStreamPos = 0;
+ uint32 bootStreamSize = 0;
+
+ for (uint i = 0; i < catNumStreams; i++) {
+ char streamType[25];
+ streamType[24] = 0;
+
+ uint32 winPosition = 0;
+ uint32 winSize = 0;
+ uint32 macPosition = 0;
+ uint32 macSize = 0;
+
+ mainSegmentStream.read(streamType, 24);
+
+ uint16 segmentIndexPlusOne = readEndian16(mainSegmentStream, isBE);
+
+ if (catRevision >= 3) {
+ macPosition = readEndian32(mainSegmentStream, isBE);
+ macSize = readEndian32(mainSegmentStream, isBE);
+ winPosition = readEndian32(mainSegmentStream, isBE);
+ winSize = readEndian32(mainSegmentStream, isBE);
+ } else {
+ winPosition = macPosition = readEndian32(mainSegmentStream, isBE);
+ winSize = macSize = readEndian32(mainSegmentStream, isBE);
}
- gameDataHandler->addPlugIns(*desc, macFiles);
+ if (mainSegmentStream.eos() || mainSegmentStream.err())
+ error("Error reading stream description");
- desc->setCursorGraphics(cursorGraphics);
- } else if (gameDesc.desc.platform == Common::kPlatformWindows) {
- Common::Array<Boot::FileIdentification> winFiles;
+ if (!strcmp(streamType, "bootstream") || !strcmp(streamType, "bootStream")) {
+ bootStreamPos = (isMac ? macPosition : winPosition);
+ bootStreamSize = (isMac ? macSize : winSize);
+
+ if (segmentIndexPlusOne != 1)
+ error("Boot stream isn't in segment 1");
- debug(1, "Attempting to boot Windows game...");
+ break;
+ }
+ }
+
+ if (!bootStreamSize)
+ error("Failed to resolve boot stream");
+
+ if (!mainSegmentStream.seek(bootStreamPos))
+ error("Failed to seek to boot stream");
+
+ // NOTE: Endianness switches from isBE to isMac here!
+ Data::DataReader streamReader(bootStreamPos, mainSegmentStream, isMac ? Data::kDataFormatMacintosh : Data::kDataFormatWindows);
+
+ uint32 shTypeID = 0;
+ uint16 shRevision = 0;
+ uint32 shPersistFlags = 0;
+ uint32 shSizeIncludingTag = 0;
+
+ if (!streamReader.readMultiple(shTypeID, shRevision, shPersistFlags, shSizeIncludingTag) || shTypeID != 1001 || shRevision != 0 || shSizeIncludingTag < 14)
+ error("Failed to read boot stream header");
+
+ if (!mainSegmentStream.skip(shSizeIncludingTag - 14))
+ error("Failed to skip stream header");
+
+ uint32 psTypeID = 0;
+ uint16 psRevision = 0;
+ uint32 psPersistFlags = 0;
+ uint32 psSizeIncludingTag = 0;
+ uint16 psUnknown1 = 0;
+
+ uint32 psResolution = 0;
+ uint16 psBitsPerPixel = 0;
+
+ if (!streamReader.readMultiple(psTypeID, psRevision, psPersistFlags, psSizeIncludingTag, psUnknown1, psResolution, psBitsPerPixel) || psTypeID != 1004 || (psRevision != 2 && psRevision != 3))
+ error("Failed to read presentation settings");
+
+ outHeight = ((psResolution >> 16) & 0xffff);
+ outWidth = (psResolution & 0xffff);
+
+ switch (psBitsPerPixel) {
+ case 1:
+ outBitDepth = 1;
+ break;
+ case 2:
+ outBitDepth = 2;
+ break;
+ case 4:
+ outBitDepth = 4;
+ break;
+ case 8:
+ outBitDepth = 8;
+ break;
+ case 16:
+ outBitDepth = 16;
+ break;
+ case 32:
+ outBitDepth = 32;
+ break;
+ default:
+ error("Unknown bit depth mode in presentation settings");
+ }
+}
+
+void resolveBitDepthAndResolutionFromPresentationSettings(Common::SeekableReadStream &mainSegmentStream, bool isMac, uint8 &outBitDepth, uint16 &outWidth, uint16 &outHeight) {
+ if (!mainSegmentStream.seek(0))
+ error("Couldn't reset main segment stream to start");
+
+ safeResolveBitDepthAndResolutionFromPresentationSettings(mainSegmentStream, isMac, outBitDepth, outWidth, outHeight);
+
+ if (!mainSegmentStream.seek(0))
+ error("Couldn't reset main segment stream to start");
+}
+
+} // End of namespace Boot
+
+
+
+BootConfiguration::BootConfiguration() : _bitDepth(0), _enhancedBitDepth(0), _width(0), _height(0) {
+}
+
+BootConfiguration bootProject(const MTropolisGameDescription &gameDesc) {
+ BootConfiguration bootConfig;
- const Boot::ManifestFile *fileDesc = bootGame->manifest;
- while (fileDesc->fileName) {
- const char *fileName = fileDesc->fileName;
+ Common::SharedPtr<ProjectDescription> &desc = bootConfig._projectDesc;
- Boot::FileIdentification ident;
- ident.fileName = fileName;
- ident.category = fileDesc->fileType;
- ident.macType.value = 0;
- ident.macCreator.value = 0;
- winFiles.push_back(ident);
+ Common::Array<Common::SharedPtr<ProjectPersistentResource>> persistentResources;
+ Common::Array<Common::SharedPtr<PlugIn> > plugIns;
+
+ Common::SharedPtr<Boot::GameDataHandler> gameDataHandler;
+
+ Common::SharedPtr<SubtitleAssetMappingTable> subsAssetMappingTable;
+ Common::SharedPtr<SubtitleModifierMappingTable> subsModifierMappingTable;
+ Common::SharedPtr<SubtitleSpeakerTable> subsSpeakerTable;
+ Common::SharedPtr<SubtitleLineTable> subsLineTable;
- fileDesc++;
+ Common::String speakerTablePath;
+ Common::String linesTablePath;
+ Common::String assetMappingTablePath;
+ Common::String modifierMappingTablePath;
+
+ const Boot::Game *bootGame = nullptr;
+ for (const Boot::Game &bootGameCandidate : Boot::Games::games) {
+ if (bootGameCandidate.bootID == gameDesc.bootID) {
+ // Multiple manifests should not have the same manifest ID!
+ assert(!bootGame);
+ bootGame = &bootGameCandidate;
}
+ }
- 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::MTFT_AUTO) {
- switch (Boot::getWinFileEndingPseudoTag(winFile.fileName)) {
- case MKTAG('.', 'm', 'p', 'l'):
- winFile.category = Boot::MTFT_MAIN;
- isWindows = true;
- isMT1 = true;
- if (isMT2)
- error("Unexpected mix of file platforms");
- break;
- case MKTAG('.', 'm', 'p', 'x'):
- winFile.category = Boot::MTFT_ADDITIONAL;
- isWindows = true;
- isMT1 = true;
- if (isMT2)
- error("Unexpected mix of file platforms");
- break;
+ if (!bootGame)
+ error("Couldn't boot mTropolis game, don't have a file manifest for manifest ID %i", static_cast<int>(gameDesc.bootID));
- case MKTAG('.', 'm', 'f', 'w'):
- winFile.category = Boot::MTFT_MAIN;
- if (isMT1 || isCrossPlatform)
- error("Unexpected mix of file platforms");
- isWindows = true;
- isMT2 = true;
- break;
+ Boot::BootScriptContext bootScriptContext(gameDesc.desc.platform == Common::kPlatformMacintosh);
- case MKTAG('.', 'm', 'x', 'w'):
- winFile.category = Boot::MTFT_ADDITIONAL;
- if (isMT1 || isCrossPlatform)
- error("Unexpected mix of file platforms");
- isWindows = true;
- isMT2 = true;
- break;
+ void (Boot::BootScriptContext::*bootFunc)() = bootGame->bootFunction;
+ (bootScriptContext.*bootFunc)();
- case MKTAG('.', 'm', 'f', 'x'):
- winFile.category = Boot::MTFT_MAIN;
- if (isWindows)
- error("Unexpected mix of file platforms");
- isCrossPlatform = true;
- isMT2 = true;
- break;
+ for (const Common::SharedPtr<Common::Archive> &arc : bootScriptContext.getPersistentArchives())
+ persistentResources.push_back(Boot::PersistentResource<Common::Archive>::wrap(arc));
- case MKTAG('.', 'm', 'x', 'x'):
- winFile.category = Boot::MTFT_ADDITIONAL;
- if (isWindows)
- error("Unexpected mix of file platforms");
- isCrossPlatform = true;
- isMT2 = true;
- break;
+ bootScriptContext.finalize();
- case MKTAG('.', 'c', '9', '5'):
- case MKTAG('.', 'e', '9', '5'):
- case MKTAG('.', 'r', '9', '5'):
- case MKTAG('.', 'x', '9', '5'):
- winFile.category = Boot::MTFT_EXTENSION;
- break;
+ Common::SharedPtr<VirtualFileSystem> vfs(new VirtualFileSystem(bootScriptContext.getVFSLayout()));
- case MKTAG('.', 'e', 'x', 'e'):
- winFile.category = Boot::MTFT_PLAYER;
- break;
+ Common::Path playerLocation;
+ Common::Path mainSegmentLocation;
+ Common::Path mainSegmentDirectory;
+ Common::Path playerDirectory;
+ Common::Path pluginsLocation;
+ Boot::PlayerType playerType = Boot::kPlayerTypeNone;
+ bool isV2Project = false;
- default:
- error("Failed to categorize input file '%s'", winFile.fileName.c_str());
- break;
- };
- }
+ persistentResources.push_back(Boot::PersistentResource<VirtualFileSystem>::wrap(vfs));
+
+ if (gameDesc.desc.platform == Common::kPlatformMacintosh) {
+ Boot::findMacPlayer(*vfs, playerLocation, playerType);
+ Boot::findMacMainSegment(*vfs, mainSegmentLocation, isV2Project);
+ } else if (gameDesc.desc.platform == Common::kPlatformWindows) {
+ Boot::findWindowsPlayer(*vfs, playerLocation, playerType);
+ Boot::findWindowsMainSegment(*vfs, mainSegmentLocation, isV2Project);
+ }
+
+ {
+ Common::StringArray pathComponents = playerLocation.splitComponents();
+ pathComponents.pop_back();
+ playerDirectory = Common::Path::joinComponents(pathComponents);
+
+ pathComponents = mainSegmentLocation.splitComponents();
+ pathComponents.pop_back();
+ mainSegmentDirectory = Common::Path::joinComponents(pathComponents);
+ }
+
+ if (isV2Project)
+ pluginsLocation = playerDirectory.appendComponent("mplugins");
+ else
+ pluginsLocation = playerDirectory.appendComponent("resource");
+
+ {
+ const Boot::ManifestSubtitlesDef &subtitlesDef = bootScriptContext.getSubtitlesDef();
+
+ linesTablePath = subtitlesDef.linesTablePath;
+ speakerTablePath = subtitlesDef.speakerTablePath;
+ assetMappingTablePath = subtitlesDef.assetMappingTablePath;
+ modifierMappingTablePath = subtitlesDef.modifierMappingTablePath;
+ }
+
+ Common::SharedPtr<CursorGraphicCollection> cursorGraphics(new CursorGraphicCollection());
+
+ // Load plug-ins
+ {
+ Common::ArchiveMemberList pluginFiles;
+ Common::Array<Common::Path> pluginPathsSorted;
+
+ const char *plugInSuffix = nullptr;
+
+ switch (playerType) {
+ case Boot::kPlayerTypeMac68k:
+ plugInSuffix = "68";
+ break;
+ case Boot::kPlayerTypeMacPPC:
+ plugInSuffix = "PP";
+ break;
+ case Boot::kPlayerTypeWin32:
+ plugInSuffix = isV2Project ? "32" : "95";
+ break;
+ case Boot::kPlayerTypeWin16:
+ plugInSuffix = isV2Project ? "16" : "31";
+ break;
+ default:
+ error("Unknown player type");
+ break;
}
- Boot::FileIdentification *mainSegmentFile = nullptr;
- Common::Array<Boot::FileIdentification *> segmentFiles;
+ vfs->listMatchingMembers(pluginFiles, pluginsLocation.appendComponent("*"));
- int addlSegments = 0;
+ for (const Common::ArchiveMemberPtr &pluginFile : pluginFiles) {
+ Common::String fileName = pluginFile->getFileName();
+ uint fnameLen = fileName.size();
- // Bin segments
- for (Boot::FileIdentification &winFile : winFiles) {
- switch (winFile.category) {
- case Boot::MTFT_PLAYER:
- // Case handled below after cursor loading
- break;
- case Boot::MTFT_EXTENSION:
- // Case handled below after cursor loading
- break;
- case Boot::MTFT_MAIN:
- mainSegmentFile = &winFile;
- break;
- case Boot::MTFT_ADDITIONAL: {
- addlSegments++;
- int segmentID = addlSegments + 1;
-
- size_t segmentIndex = static_cast<size_t>(segmentID - 1);
- while (segmentFiles.size() <= segmentIndex)
- segmentFiles.push_back(nullptr);
- segmentFiles[segmentIndex] = &winFile;
- } break;
- case Boot::MTFT_VIDEO:
- break;
- case Boot::MTFT_SPECIAL:
- break;
- case Boot::MTFT_AUTO:
- break;
- }
+ if (fnameLen >= 4 && fileName[fnameLen - 4] == '.' && fileName[fnameLen - 2] == plugInSuffix[0] && fileName[fnameLen - 1] == plugInSuffix[1])
+ pluginPathsSorted.push_back(pluginFile->getPathInArchive());
}
- if (segmentFiles.size() > 0)
- segmentFiles[0] = mainSegmentFile;
- else
- segmentFiles.push_back(mainSegmentFile);
+ // This is possibly not optimal - Sort order on MacOS is based on the MacRoman encoded file name,
+ // and possibly case-sensitive too. Sort order on Windows is case-insensitive. However, we don't
+ // want to rely on the filenames having the correct case on the user machine.
+ Common::sort(pluginPathsSorted.begin(), pluginPathsSorted.end(), Boot::sortPathFileName);
+
+ if (gameDesc.desc.platform == Common::kPlatformMacintosh) {
+ Boot::loadCursorsMac(*vfs, playerLocation, *cursorGraphics);
- // Load cursors
- Common::SharedPtr<CursorGraphicCollection> cursorGraphics(new CursorGraphicCollection());
+ for (const Common::Path &plugInPath : pluginPathsSorted)
+ Boot::loadCursorsMac(*vfs, plugInPath, *cursorGraphics);
+ } else if (gameDesc.desc.platform == Common::kPlatformWindows) {
+ Boot::loadCursorsWin(*vfs, playerLocation, *cursorGraphics);
- for (Boot::FileIdentification &winFile : winFiles) {
- if (winFile.category == Boot::MTFT_PLAYER)
- Boot::loadCursorsWin(winFile, *cursorGraphics);
+ for (const Common::Path &plugInPath : pluginPathsSorted)
+ Boot::loadCursorsWin(*vfs, plugInPath, *cursorGraphics);
}
+ }
- for (Boot::FileIdentification &winFile : winFiles) {
- if (winFile.category == Boot::MTFT_EXTENSION)
- Boot::loadCursorsWin(winFile, *cursorGraphics);
+ // Add ScummVM plug-ins from the boot script
+ for (Boot::BootScriptContext::PlugIn plugIn : bootScriptContext.getPlugIns()) {
+ switch (plugIn) {
+ case Boot::BootScriptContext::kPlugInStandard:
+ plugIns.push_back(Boot::loadStandardPlugIn(gameDesc));
+ break;
+ case Boot::BootScriptContext::kPlugInObsidian:
+ plugIns.push_back(Boot::loadObsidianPlugIn(gameDesc, *vfs, pluginsLocation));
+ break;
+ case Boot::BootScriptContext::kPlugInMTI:
+ plugIns.push_back(Boot::loadMTIPlugIn(gameDesc));
+ break;
+ default:
+ error("Unknown plug-in ID");
}
+ }
- // Create the project description
- desc.reset(new ProjectDescription(isCrossPlatform ? KProjectPlatformCrossPlatform : kProjectPlatformWindows));
+ ProjectPlatform projectPlatform = (gameDesc.desc.platform == Common::kPlatformMacintosh) ? kProjectPlatformMacintosh : kProjectPlatformWindows;
- for (Boot::FileIdentification *segmentFile : segmentFiles) {
- if (!segmentFile)
- error("Missing segment file");
+ desc.reset(new ProjectDescription(projectPlatform, isV2Project ? kProjectMajorVersion2 : kProjectMajorVersion1, vfs.get(), mainSegmentDirectory));
+ desc->setCursorGraphics(cursorGraphics);
- 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());
- }
- }
+ for (const Common::SharedPtr<PlugIn> &plugIn : plugIns)
+ desc->addPlugIn(plugIn);
+
+ Common::SharedPtr<Common::SeekableReadStream> mainSegmentStream;
- gameDataHandler->addPlugIns(*desc, winFiles);
+ if (gameDesc.desc.platform == Common::kPlatformMacintosh)
+ mainSegmentStream.reset(Common::MacResManager::openFileOrDataFork(mainSegmentLocation, *vfs));
+ else if (gameDesc.desc.platform == Common::kPlatformWindows)
+ mainSegmentStream.reset(vfs->createReadStreamForMember(mainSegmentLocation));
- desc->setCursorGraphics(cursorGraphics);
+ if (!mainSegmentStream)
+ error("Failed to open main segment");
+
+ persistentResources.push_back(Boot::PersistentResource<Common::SeekableReadStream>::wrap(mainSegmentStream));
+
+ desc->addSegment(0, mainSegmentStream.get());
+ desc->setLanguage(gameDesc.desc.language);
+
+ Common::Point resolution = bootScriptContext.getResolution();
+
+ if (bootScriptContext.getBitDepth() == Boot::BootScriptContext::kBitDepthAuto || resolution.x == 0 || resolution.y == 0) {
+ uint16 width = 0;
+ uint16 height = 0;
+ Boot::resolveBitDepthAndResolutionFromPresentationSettings(*mainSegmentStream, gameDesc.desc.platform == Common::kPlatformMacintosh, bootConfig._bitDepth, width, height);
+
+ if (resolution.x == 0)
+ resolution.x = width;
+
+ if (resolution.y == 0)
+ resolution.y = height;
}
+ bootConfig._width = resolution.x;
+ bootConfig._height = resolution.y;
+
+ switch (bootScriptContext.getBitDepth()) {
+ case Boot::BootScriptContext::kBitDepthAuto:
+ break;
+ case Boot::BootScriptContext::kBitDepth8:
+ bootConfig._bitDepth = 8;
+ break;
+ case Boot::BootScriptContext::kBitDepth16:
+ bootConfig._bitDepth = 16;
+ break;
+ case Boot::BootScriptContext::kBitDepth32:
+ bootConfig._bitDepth = 32;
+ break;
+ default:
+ error("Invalid bit depth in boot script");
+ };
+
+ switch (bootScriptContext.getEnhancedBitDepth()) {
+ case Boot::BootScriptContext::kBitDepthAuto:
+ bootConfig._enhancedBitDepth = bootConfig._bitDepth;
+ bootConfig._enhancedBitDepth = 32;
+ break;
+ case Boot::BootScriptContext::kBitDepth8:
+ bootConfig._enhancedBitDepth = 8;
+ break;
+ case Boot::BootScriptContext::kBitDepth16:
+ bootConfig._enhancedBitDepth = 16;
+ break;
+ case Boot::BootScriptContext::kBitDepth32:
+ bootConfig._enhancedBitDepth = 32;
+ break;
+ default:
+ error("Invalid bit depth in boot script");
+ };
+
+ if (bootConfig._enhancedBitDepth < bootConfig._bitDepth)
+ bootConfig._enhancedBitDepth = bootConfig._bitDepth;
+
Common::SharedPtr<ProjectResources> resources(new ProjectResources());
resources->persistentResources = persistentResources;
@@ -1658,9 +2894,9 @@ Common::SharedPtr<ProjectDescription> bootProject(const MTropolisGameDescription
subTables.modifierMapping = subsModifierMappingTable;
subTables.speakers = subsSpeakerTable;
- desc->getSubtitles(subTables);
+ desc->setSubtitles(subTables);
- return desc;
+ return bootConfig;
}
void bootAddSearchPaths(const Common::FSNode &gameDataDir, const MTropolisGameDescription &gameDesc) {
@@ -1676,16 +2912,6 @@ void bootAddSearchPaths(const Common::FSNode &gameDataDir, const MTropolisGameDe
if (!bootGame)
error("Couldn't boot mTropolis game, don't have a file manifest for manifest ID %i", static_cast<int>(gameDesc.bootID));
-
- if (!bootGame->directories)
- return;
-
- size_t index = 0;
- while (bootGame->directories[index]) {
- const char *directoryPath = bootGame->directories[index++];
-
- SearchMan.addSubDirectoryMatching(gameDataDir, directoryPath);
- }
}
} // End of namespace MTropolis
diff --git a/engines/mtropolis/boot.h b/engines/mtropolis/boot.h
index 5cb721a76ee..69e9517e0f0 100644
--- a/engines/mtropolis/boot.h
+++ b/engines/mtropolis/boot.h
@@ -35,7 +35,18 @@ namespace MTropolis {
struct MTropolisGameDescription;
class ProjectDescription;
-Common::SharedPtr<ProjectDescription> bootProject(const MTropolisGameDescription &gameDesc);
+struct BootConfiguration {
+ BootConfiguration();
+
+ uint8 _bitDepth;
+ uint8 _enhancedBitDepth;
+ uint16 _width;
+ uint16 _height;
+
+ Common::SharedPtr<ProjectDescription> _projectDesc;
+};
+
+BootConfiguration bootProject(const MTropolisGameDescription &gameDesc);
void bootAddSearchPaths(const Common::FSNode &gameDataDir, const MTropolisGameDescription &gameDesc);
} // End of namespace MTropolis
diff --git a/engines/mtropolis/data.cpp b/engines/mtropolis/data.cpp
index c1258808d1b..c859cc71dfc 100644
--- a/engines/mtropolis/data.cpp
+++ b/engines/mtropolis/data.cpp
@@ -157,66 +157,54 @@ bool isAsset(DataObjectType type) {
} // End of namespace DataObjectTypes
-DataReader::DataReader(int64 globalPosition, Common::SeekableReadStreamEndian &stream, ProjectFormat projectFormat,
- ProjectEngineVersion projectEngineVersion)
- : _globalPosition(globalPosition), _stream(stream), _projectFormat(projectFormat), _permitDamagedStrings(false),
- _projectEngineVersion(projectEngineVersion) {
+DataReader::DataReader(int64 globalPosition, Common::SeekableReadStream &stream, DataFormat dataFormat)
+ : _globalPosition(globalPosition), _stream(stream), _dataFormat(dataFormat), _permitDamagedStrings(false) {
}
bool DataReader::readU8(uint8 &value) {
- value = _stream.readByte();
- return checkErrorAndReset();
+ return readMultiple(value);
}
bool DataReader::readU16(uint16 &value) {
- value = _stream.readUint16();
- return checkErrorAndReset();
+ return readMultiple(value);
}
bool DataReader::readU32(uint32 &value) {
- value = _stream.readUint32();
- return checkErrorAndReset();
+ return readMultiple(value);
}
bool DataReader::readU64(uint64 &value) {
- value = _stream.readUint64();
- return checkErrorAndReset();
+ return readMultiple(value);
}
bool DataReader::readS8(int8 &value) {
- value = _stream.readSByte();
- return checkErrorAndReset();
+ return readMultiple(value);
}
bool DataReader::readS16(int16 &value) {
- value = _stream.readSint16();
- return checkErrorAndReset();
+ return readMultiple(value);
}
bool DataReader::readS32(int32 &value) {
- value = _stream.readSint32();
- return checkErrorAndReset();
+ return readMultiple(value);
}
bool DataReader::readS64(int64 &value) {
- value = _stream.readSint64();
- return checkErrorAndReset();
+ return readMultiple(value);
}
bool DataReader::readF32(float &value) {
- value = _stream.readFloat();
- return checkErrorAndReset();
+ return readMultiple(value);
}
bool DataReader::readF64(double &value) {
- value = _stream.readDouble();
- return checkErrorAndReset();
+ return readMultiple(value);
}
bool DataReader::readPlatformFloat(Common::XPFloat &value) {
- if (_projectFormat == kProjectFormatMacintosh) {
- return readU16(value.signAndExponent) && readU64(value.mantissa);
- } else if (_projectFormat == kProjectFormatWindows) {
+ if (_dataFormat == kDataFormatMacintosh) {
+ return readMultiple(value.signAndExponent, value.mantissa);
+ } else if (_dataFormat == kDataFormatWindows) {
uint64 bits;
if (!readU64(bits))
return false;
@@ -301,20 +289,8 @@ int64 DataReader::tell() const {
return _stream.pos();
}
-ProjectFormat DataReader::getProjectFormat() const {
- return _projectFormat;
-}
-
-ProjectEngineVersion DataReader::getProjectEngineVersion() const {
- return _projectEngineVersion;
-}
-
-bool DataReader::isBigEndian() const {
- return _stream.isBE();
-}
-
-bool DataReader::isV2Project() const {
- return _projectEngineVersion == kProjectEngineVersion2;
+DataFormat DataReader::getDataFormat() const {
+ return _dataFormat;
}
void DataReader::setPermitDamagedStrings(bool permit) {
@@ -333,12 +309,14 @@ bool DataReader::checkErrorAndReset() {
bool Rect::load(DataReader &reader) {
- if (reader.getProjectFormat() == kProjectFormatMacintosh)
+ switch (reader.getDataFormat()) {
+ case kDataFormatMacintosh:
return reader.readS16(top) && reader.readS16(left) && reader.readS16(bottom) && reader.readS16(right);
- else if (reader.getProjectFormat() == kProjectFormatWindows)
+ case kDataFormatWindows:
return reader.readS16(left) && reader.readS16(top) && reader.readS16(right) && reader.readS16(bottom);
- else
+ default:
return false;
+ };
}
Rect::Rect() : top(0), left(0), bottom(0), right(0) {
@@ -363,12 +341,14 @@ Point::Point() : x(0), y(0) {
}
bool Point::load(DataReader &reader) {
- if (reader.getProjectFormat() == kProjectFormatMacintosh)
+ switch (reader.getDataFormat()) {
+ case kDataFormatMacintosh:
return reader.readS16(y) && reader.readS16(x);
- else if (reader.getProjectFormat() == kProjectFormatWindows)
+ case kDataFormatWindows:
return reader.readS16(x) && reader.readS16(y);
- else
+ default:
return false;
+ };
}
bool Point::toScummVMPoint(Common::Point &outPoint) const {
@@ -388,9 +368,9 @@ ColorRGB16::ColorRGB16() : red(0), green(0), blue(0) {
bool ColorRGB16::load(DataReader& reader) {
- if (reader.getProjectFormat() == kProjectFormatMacintosh)
+ if (reader.getDataFormat() == kDataFormatMacintosh)
return reader.readU16(red) && reader.readU16(green) && reader.readU16(blue);
- else if (reader.getProjectFormat() == kProjectFormatWindows) {
+ else if (reader.getDataFormat() == kDataFormatWindows) {
uint8 bgra[4];
if (!reader.readBytes(bgra))
return false;
@@ -451,9 +431,9 @@ bool InternalTypeTaggedValue::load(DataReader &reader) {
if (!reader.readBytes(contents))
return false;
- Common::MemoryReadStreamEndian contentsStream(contents, sizeof(contents), reader.isBigEndian());
+ Common::MemoryReadStream contentsStream(contents, sizeof(contents));
- DataReader valueReader(valueGlobalPos, contentsStream, reader.getProjectFormat(), reader.getProjectEngineVersion());
+ DataReader valueReader(valueGlobalPos, contentsStream, reader.getDataFormat());
switch (type) {
case kNull:
@@ -932,10 +912,10 @@ TextLabelElement::TextLabelElement()
}
DataReadErrorCode TextLabelElement::load(DataReader &reader) {
- if (reader.getProjectFormat() == kProjectFormatMacintosh) {
+ if (reader.getDataFormat() == kDataFormatMacintosh) {
if (_revision != 2)
return kDataReadErrorUnsupportedRevision;
- } else if (reader.getProjectFormat() == kProjectFormatWindows) {
+ } else if (reader.getDataFormat() == kDataFormatWindows) {
if (_revision != 0)
return kDataReadErrorUnsupportedRevision;
} else
@@ -948,7 +928,7 @@ DataReadErrorCode TextLabelElement::load(DataReader &reader) {
haveMacPart = false;
haveWinPart = false;
- if (reader.getProjectFormat() == kProjectFormatWindows) {
+ if (reader.getDataFormat() == kDataFormatWindows) {
haveWinPart = true;
if (!reader.readBytes(platform.win.unknown3))
return kDataReadErrorReadFailed;
@@ -957,10 +937,10 @@ DataReadErrorCode TextLabelElement::load(DataReader &reader) {
if (!rect1.load(reader) || !rect2.load(reader) || !reader.readU32(assetID))
return kDataReadErrorReadFailed;
- if (reader.getProjectFormat() == kProjectFormatWindows) {
+ if (reader.getDataFormat() == kDataFormatWindows) {
if (!reader.readBytes(platform.win.unknown4))
return kDataReadErrorReadFailed;
- } else if (reader.getProjectFormat() == kProjectFormatMacintosh) {
+ } else if (reader.getDataFormat() == kDataFormatMacintosh) {
haveMacPart = true;
if (!reader.readBytes(platform.mac.unknown2))
return kDataReadErrorReadFailed;
@@ -1055,7 +1035,7 @@ DataReadErrorCode GlobalObjectInfo::load(DataReader &reader) {
ProjectCatalog::StreamDesc::StreamDesc()
: streamType { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
- segmentIndexPlusOne(0), size(0), pos(0) {
+ segmentIndexPlusOne(0), winSize(0), winPos(0), macSize(0), macPos(0) {
}
ProjectCatalog::SegmentDesc::SegmentDesc() : segmentID(0) {
@@ -1089,16 +1069,27 @@ DataReadErrorCode ProjectCatalog::load(DataReader &reader) {
return kDataReadErrorReadFailed;
}
- if (_revision >= 3 && reader.getProjectFormat() == Data::kProjectFormatWindows && !reader.skip(8)) {
- return kDataReadErrorReadFailed;
- }
-
- if (!reader.readU32(streamDesc.pos) || !reader.readU32(streamDesc.size)) {
- return kDataReadErrorReadFailed;
- }
+ if (_revision >= 3) {
+ if (!reader.readMultiple(streamDesc.macPos, streamDesc.macSize, streamDesc.winPos, streamDesc.winSize))
+ return kDataReadErrorReadFailed;
+ } else {
+ uint32 pos = 0;
+ uint32 size = 0;
+ if (!reader.readMultiple(pos, size))
+ return kDataReadErrorReadFailed;
- if (_revision >= 3 && reader.getProjectFormat() == Data::kProjectFormatMacintosh && !reader.skip(8)) {
- return kDataReadErrorReadFailed;
+ streamDesc.winPos = 0;
+ streamDesc.winSize = 0;
+ streamDesc.macPos = 0;
+ streamDesc.macSize = 0;
+
+ if (reader.getDataFormat() == Data::kDataFormatWindows) {
+ streamDesc.winPos = pos;
+ streamDesc.winSize = size;
+ } else if (reader.getDataFormat() == Data::kDataFormatMacintosh) {
+ streamDesc.macPos = pos;
+ streamDesc.macSize = size;
+ }
}
}
@@ -1149,7 +1140,7 @@ DataReadErrorCode StreamHeader::load(DataReader& reader) {
BehaviorModifier::BehaviorModifier()
: modifierFlags(0), sizeIncludingTag(0), unknown2{0, 0}, guid(0), unknown4(0), unknown5(0), unknown6(0),
- lengthOfName(0), numChildren(0), behaviorFlags(0), unknown7{0, 0} {
+ lengthOfName(0), numChildren(0), behaviorFlags(0), unknown7{0, 0}, unknown8(0) {
}
DataReadErrorCode BehaviorModifier::load(DataReader& reader) {
@@ -1163,7 +1154,7 @@ DataReadErrorCode BehaviorModifier::load(DataReader& reader) {
|| !reader.readU16(lengthOfName) || !reader.readU16(numChildren))
return kDataReadErrorReadFailed;
- if (_revision == 2 && !reader.readU32(unknown8))
+ if (_revision >= 2 && !reader.readU32(unknown8))
return kDataReadErrorReadFailed;
if (lengthOfName > 0 && !reader.readTerminatedStr(name, lengthOfName))
@@ -1186,13 +1177,11 @@ MiniscriptProgram::Attribute::Attribute()
MiniscriptProgram::MiniscriptProgram()
: unknown1(0), sizeOfInstructions(0), numOfInstructions(0), numLocalRefs(0), numAttributes(0),
- projectFormat(kProjectFormatUnknown), isBigEndian(false),
- projectEngineVersion(kProjectEngineVersionUnknown) {
+ dataFormat(kDataFormatUnknown) {
}
bool MiniscriptProgram::load(DataReader &reader) {
- projectFormat = reader.getProjectFormat();
- isBigEndian = reader.isBigEndian();
+ dataFormat = reader.getDataFormat();
if (!reader.readU32(unknown1) || !reader.readU32(sizeOfInstructions) || !reader.readU32(numOfInstructions) || !reader.readU32(numLocalRefs) || !reader.readU32(numAttributes))
return false;
@@ -1235,13 +1224,13 @@ TypicalModifierHeader::TypicalModifierHeader()
unknown5(0), lengthOfName(0) {
}
-bool TypicalModifierHeader::load(DataReader& reader) {
+bool TypicalModifierHeader::load(DataReader& reader, bool isV2) {
if (!reader.readU32(modifierFlags) || !reader.readU32(sizeIncludingTag) || !reader.readU32(guid)
|| !reader.readBytes(unknown3) || !reader.readU32(unknown4) || !editorLayoutPosition.load(reader)
|| !reader.readU16(lengthOfName))
return false;
- if (reader.isV2Project() && !reader.readU32(unknown5))
+ if (isV2 && !reader.readU32(unknown5))
return false;
if (lengthOfName > 0 && !reader.readTerminatedStr(name, lengthOfName))
@@ -1258,7 +1247,7 @@ DataReadErrorCode MiniscriptModifier::load(DataReader &reader) {
if (_revision != 1003 && _revision != 2003)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !enableWhen.load(reader) || !reader.readBytes(unknown6) || !reader.readU8(unknown7) || !program.load(reader))
+ if (!modHeader.load(reader, _revision >= 2000) || !enableWhen.load(reader) || !reader.readBytes(unknown6) || !reader.readU8(unknown7) || !program.load(reader))
return kDataReadErrorReadFailed;
return kDataReadErrorNone;
@@ -1271,7 +1260,7 @@ DataReadErrorCode ColorTableModifier::load(DataReader &reader) {
if (_revision != 1001)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !applyWhen.load(reader) || !reader.readU32(unknown1)
+ if (!modHeader.load(reader, _revision >= 2000) || !applyWhen.load(reader) || !reader.readU32(unknown1)
|| !reader.readBytes(unknown2) || !reader.readU32(assetID))
return kDataReadErrorReadFailed;
@@ -1286,7 +1275,7 @@ DataReadErrorCode SoundFadeModifier::load(DataReader &reader) {
if (_revision != 1000)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader))
+ if (!modHeader.load(reader, _revision >= 2000)) // TODO: Test
return kDataReadErrorReadFailed;
if (!reader.readBytes(unknown1) || !enableWhen.load(reader) || !disableWhen.load(reader)
@@ -1304,7 +1293,7 @@ DataReadErrorCode SaveAndRestoreModifier::load(DataReader &reader) {
if (_revision != 1001)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !reader.readBytes(unknown1) || !saveWhen.load(reader) || !restoreWhen.load(reader)
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readBytes(unknown1) || !saveWhen.load(reader) || !restoreWhen.load(reader)
|| !saveOrRestoreValue.load(reader) || !reader.readBytes(unknown5) || !reader.readU8(lengthOfFilePath)
|| !reader.readU8(lengthOfFileName) || !reader.readU8(lengthOfVariableName) || !reader.readU8(lengthOfVariableString)
|| !reader.readNonTerminatedStr(varName, lengthOfVariableName) || !reader.readNonTerminatedStr(varString, lengthOfVariableString)
@@ -1323,7 +1312,7 @@ DataReadErrorCode MessengerModifier::load(DataReader &reader) {
if (_revision != 1002 && _revision != 2002)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader))
+ if (!modHeader.load(reader, _revision >= 2000))
return kDataReadErrorReadFailed;
// Unlike most cases, the "when" event is split in half in this case
@@ -1347,7 +1336,7 @@ DataReadErrorCode SetModifier::load(DataReader &reader) {
return kDataReadErrorUnsupportedRevision;
// NOTE: executeWhen is split in half and stored in 2 separate parts
- if (!modHeader.load(reader) || !reader.readBytes(unknown1) || !reader.readU32(executeWhen.eventID)
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readBytes(unknown1) || !reader.readU32(executeWhen.eventID)
|| !source.load(reader) || !target.load(reader) || !reader.readU32(executeWhen.eventInfo) || !reader.readU8(unknown3)
|| !reader.readU8(sourceNameLength) || !reader.readU8(targetNameLength) || !reader.readU8(sourceStringLength)
|| !reader.readU8(targetStringLength) || !reader.readU8(unknown4) || !reader.readNonTerminatedStr(sourceName, sourceNameLength)
@@ -1364,7 +1353,7 @@ AliasModifier::AliasModifier()
}
DataReadErrorCode AliasModifier::load(DataReader& reader) {
- if (_revision > 2 && _revision != 4)
+ if (_revision != 0 && _revision != 1 && _revision != 2 && _revision != 4)
return kDataReadErrorUnsupportedRevision;
if (!reader.readU32(modifierFlags)
@@ -1374,14 +1363,17 @@ DataReadErrorCode AliasModifier::load(DataReader& reader) {
|| !reader.readU32(unknown2))
return kDataReadErrorReadFailed;
- if (_revision <= 2 && (!reader.readU16(lengthOfName)
- || !editorLayoutPosition.load(reader)))
- return kDataReadErrorReadFailed;
- else if (_revision == 4 && !editorLayoutPosition.load(reader)) {
- // v4 puts the name len just before it.
- return kDataReadErrorReadFailed;
+ if (_revision <= 2) {
+ uint16 nameLen = 0;
+ if (!reader.readU16(nameLen))
+ return kDataReadErrorReadFailed;
+
+ lengthOfName = nameLen;
}
+ if (!editorLayoutPosition.load(reader))
+ return kDataReadErrorReadFailed;
+
if (_revision >= 2) {
haveGUID = true;
if (!reader.readU32(guid))
@@ -1391,11 +1383,9 @@ DataReadErrorCode AliasModifier::load(DataReader& reader) {
guid = 0;
}
- if (_revision == 4) {
- uint32 nameLen = 0;
- if (!reader.readU32(nameLen))
+ if (_revision >= 4) {
+ if (!reader.readU32(lengthOfName))
return kDataReadErrorReadFailed;
- lengthOfName = static_cast<uint16>(nameLen);
}
if (!reader.readTerminatedStr(name, lengthOfName))
@@ -1412,7 +1402,7 @@ DataReadErrorCode ChangeSceneModifier::load(DataReader &reader) {
if (_revision != 1001 && _revision != 2001)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !reader.readU32(changeSceneFlags) || !executeWhen.load(reader)
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readU32(changeSceneFlags) || !executeWhen.load(reader)
|| !reader.readU32(targetSectionGUID) || !reader.readU32(targetSubsectionGUID) || !reader.readU32(targetSceneGUID))
return kDataReadErrorReadFailed;
@@ -1427,7 +1417,7 @@ DataReadErrorCode SoundEffectModifier::load(DataReader &reader) {
if (_revision != 1000)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !reader.readBytes(unknown1) || !executeWhen.load(reader)
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readBytes(unknown1) || !executeWhen.load(reader)
|| !terminateWhen.load(reader) || !reader.readU32(unknown2) || !reader.readBytes(unknown3)
|| !reader.readU32(assetID) || !reader.readBytes(unknown5))
return kDataReadErrorReadFailed;
@@ -1479,7 +1469,7 @@ DataReadErrorCode PathMotionModifier::load(DataReader &reader) {
if (_revision != 1001)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader)
+ if (!modHeader.load(reader, _revision >= 2000)
|| !reader.readU32(flags)
|| !executeWhen.load(reader)
|| !terminateWhen.load(reader)
@@ -1509,7 +1499,7 @@ DataReadErrorCode SimpleMotionModifier::load(DataReader &reader) {
if (_revision != 1001)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader))
+ if (!modHeader.load(reader, _revision >= 2000))
return kDataReadErrorReadFailed;
if (!executeWhen.load(reader) || !terminateWhen.load(reader) || !reader.readU16(motionType) || !reader.readU16(directionFlags)
@@ -1528,13 +1518,13 @@ DataReadErrorCode DragMotionModifier::load(DataReader &reader) {
if (_revision != 1000)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader))
+ if (!modHeader.load(reader, _revision >= 2000))
return kDataReadErrorReadFailed;
if (!enableWhen.load(reader) || !disableWhen.load(reader))
return kDataReadErrorReadFailed;
- if (reader.getProjectFormat() == kProjectFormatMacintosh) {
+ if (reader.getDataFormat() == kDataFormatMacintosh) {
if (!reader.readU8(platform.mac.flags) || !reader.readU8(platform.mac.unknown3))
return kDataReadErrorReadFailed;
@@ -1542,7 +1532,7 @@ DataReadErrorCode DragMotionModifier::load(DataReader &reader) {
} else
haveMacPart = false;
- if (reader.getProjectFormat() == kProjectFormatWindows) {
+ if (reader.getDataFormat() == kDataFormatWindows) {
if (!reader.readU8(platform.win.unknown2) || !reader.readU8(platform.win.constrainHorizontal)
|| !reader.readU8(platform.win.constrainVertical) || !reader.readU8(platform.win.constrainToParent))
return kDataReadErrorReadFailed;
@@ -1565,7 +1555,7 @@ DataReadErrorCode VectorMotionModifier::load(DataReader &reader) {
if (_revision != 1001)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader))
+ if (!modHeader.load(reader, _revision >= 2000))
return kDataReadErrorReadFailed;
if (!enableWhen.load(reader) || !disableWhen.load(reader) || !vec.load(reader)
@@ -1585,7 +1575,7 @@ DataReadErrorCode SceneTransitionModifier::load(DataReader &reader) {
if (_revision != 1001)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader))
+ if (!modHeader.load(reader, _revision >= 2000))
return kDataReadErrorReadFailed;
if (!enableWhen.load(reader) || !disableWhen.load(reader) || !reader.readU16(transitionType)
@@ -1605,7 +1595,7 @@ DataReadErrorCode ElementTransitionModifier::load(DataReader &reader) {
if (_revision != 1001)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader))
+ if (!modHeader.load(reader, _revision >= 2000))
return kDataReadErrorReadFailed;
if (!enableWhen.load(reader) || !disableWhen.load(reader) || !reader.readU16(revealType)
@@ -1624,7 +1614,7 @@ DataReadErrorCode SharedSceneModifier::load(DataReader &reader) {
if (_revision != 1000)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !reader.readBytes(unknown1)
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readBytes(unknown1)
|| !executeWhen.load(reader) || !reader.readU32(sectionGUID)
|| !reader.readU32(subsectionGUID) || !reader.readU32(sceneGUID))
return kDataReadErrorReadFailed;
@@ -1641,7 +1631,7 @@ DataReadErrorCode IfMessengerModifier::load(DataReader &reader) {
if (_revision != 1002)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !reader.readU32(messageFlags) || !when.load(reader) || !send.load(reader)
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readU32(messageFlags) || !when.load(reader) || !send.load(reader)
|| !reader.readU16(unknown6) || !reader.readU32(destination) || !reader.readBytes(unknown7) || !with.load(reader)
|| !reader.readBytes(unknown9) || !reader.readU8(withSourceLength) || !reader.readU8(withStringLength))
return kDataReadErrorReadFailed;
@@ -1664,7 +1654,7 @@ DataReadErrorCode TimerMessengerModifier::load(DataReader &reader) {
if (_revision != 1002 && _revision != 2002)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader))
+ if (!modHeader.load(reader, _revision >= 2000))
return kDataReadErrorReadFailed;
if (!reader.readU32(messageAndTimerFlags) || !executeWhen.load(reader) || !send.load(reader)
@@ -1687,7 +1677,7 @@ DataReadErrorCode BoundaryDetectionMessengerModifier::load(DataReader &reader) {
if (_revision != 1002)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader))
+ if (!modHeader.load(reader, _revision >= 2000))
return kDataReadErrorReadFailed;
if (!reader.readU16(messageFlagsHigh) || !enableWhen.load(reader) || !disableWhen.load(reader)
@@ -1708,7 +1698,7 @@ DataReadErrorCode CollisionDetectionMessengerModifier::load(DataReader &reader)
if (_revision != 1002)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader))
+ if (!modHeader.load(reader, _revision >= 2000))
return kDataReadErrorReadFailed;
if (!reader.readU32(messageAndModifierFlags) || !enableWhen.load(reader) || !disableWhen.load(reader)
@@ -1730,7 +1720,7 @@ DataReadErrorCode KeyboardMessengerModifier::load(DataReader &reader) {
if (_revision != 1003 && _revision != 2003)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !reader.readU32(messageFlagsAndKeyStates) || !reader.readU16(unknown2)
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readU32(messageFlagsAndKeyStates) || !reader.readU16(unknown2)
|| !reader.readU16(keyModifiers) || !reader.readU8(keycode) || !reader.readBytes(unknown4)
|| !message.load(reader) || !reader.readU16(unknown7) || !reader.readU32(destination)
|| !reader.readBytes(unknown9) || !with.load(reader) || !reader.readU8(withSourceLength)
@@ -1751,7 +1741,7 @@ DataReadErrorCode TextStyleModifier::load(DataReader &reader) {
if (_revision != 1000)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !reader.readBytes(unknown1) || !reader.readU16(macFontID)
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readBytes(unknown1) || !reader.readU16(macFontID)
|| !reader.readU8(flags) || !reader.readU8(unknown2) || !reader.readU16(size)
|| !textColor.load(reader) || !backgroundColor.load(reader) || !reader.readU16(alignment)
|| !reader.readU16(unknown3) || !applyWhen.load(reader) || !removeWhen.load(reader)
@@ -1771,12 +1761,12 @@ DataReadErrorCode GraphicModifier::load(DataReader &reader) {
if (_revision != 1001 && _revision != 2001)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !reader.readU16(unknown1) || !applyWhen.load(reader)
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readU16(unknown1) || !applyWhen.load(reader)
|| !removeWhen.load(reader) || !reader.readBytes(unknown2) || !reader.readU16(inkMode)
|| !reader.readU16(shape))
return kDataReadErrorReadFailed;
- if (reader.getProjectFormat() == kProjectFormatMacintosh) {
+ if (reader.getDataFormat() == kDataFormatMacintosh) {
haveMacPart = true;
if (!reader.readBytes(platform.mac.unknown4_1) || !backColor.load(reader) || !foreColor.load(reader)
|| !reader.readU16(borderSize) || !borderColor.load(reader) || !reader.readU16(shadowSize)
@@ -1785,7 +1775,7 @@ DataReadErrorCode GraphicModifier::load(DataReader &reader) {
} else
haveMacPart = false;
- if (reader.getProjectFormat() == kProjectFormatWindows) {
+ if (reader.getDataFormat() == kDataFormatWindows) {
haveWinPart = true;
if (!reader.readBytes(platform.win.unknown5_1) || !backColor.load(reader) || !foreColor.load(reader)
|| !reader.readU16(borderSize) || !borderColor.load(reader) || !reader.readU16(shadowSize)
@@ -1814,7 +1804,7 @@ DataReadErrorCode ImageEffectModifier::load(DataReader &reader) {
if (_revision != 1000)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !reader.readU32(flags) || !reader.readU16(type) || !applyWhen.load(reader) || !removeWhen.load(reader)
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readU32(flags) || !reader.readU16(type) || !applyWhen.load(reader) || !removeWhen.load(reader)
|| !reader.readU16(bevelWidth) || !reader.readU16(toneAmount) || !reader.readBytes(unknown2))
return kDataReadErrorReadFailed;
@@ -1828,7 +1818,7 @@ DataReadErrorCode ReturnModifier::load(DataReader &reader) {
if (_revision != 1001)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !executeWhen.load(reader) || !reader.readU16(unknown1))
+ if (!modHeader.load(reader, _revision >= 2000) || !executeWhen.load(reader) || !reader.readU16(unknown1))
return kDataReadErrorReadFailed;
return kDataReadErrorNone;
@@ -1843,11 +1833,11 @@ DataReadErrorCode CursorModifierV1::load(DataReader &reader) {
int64 startPos = reader.tell();
- if (!modHeader.load(reader))
+ if (!modHeader.load(reader, _revision >= 2000))
return kDataReadErrorReadFailed;
int64 distFromStart = reader.tell() - startPos + 6;
- if (reader.getProjectFormat() == kProjectFormatMacintosh || modHeader.sizeIncludingTag > distFromStart) {
+ if (reader.getDataFormat() == kDataFormatMacintosh || modHeader.sizeIncludingTag > distFromStart) {
hasMacOnlyPart = true;
if (!macOnlyPart.applyWhen.load(reader) || !reader.readU32(macOnlyPart.unknown1) || !reader.readU16(macOnlyPart.unknown2) || !reader.readU32(macOnlyPart.cursorIndex))
@@ -1885,7 +1875,7 @@ DataReadErrorCode BooleanVariableModifier::load(DataReader &reader) {
if (_revision != 1000)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !reader.readU8(value) || !reader.readU8(unknown5))
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readU8(value) || !reader.readU8(unknown5))
return kDataReadErrorReadFailed;
return kDataReadErrorNone;
@@ -1898,7 +1888,7 @@ DataReadErrorCode IntegerVariableModifier::load(DataReader &reader) {
if (_revision != 1000 && _revision != 2000)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !reader.readBytes(unknown1) || !reader.readS32(value))
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readBytes(unknown1) || !reader.readS32(value))
return kDataReadErrorReadFailed;
return kDataReadErrorNone;
@@ -1911,7 +1901,7 @@ DataReadErrorCode IntegerRangeVariableModifier::load(DataReader &reader) {
if (_revision != 1000 && _revision != 2000)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !reader.readBytes(unknown1) || !range.load(reader))
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readBytes(unknown1) || !range.load(reader))
return kDataReadErrorReadFailed;
return kDataReadErrorNone;
@@ -1924,7 +1914,7 @@ DataReadErrorCode VectorVariableModifier::load(DataReader &reader) {
if (_revision != 1000 && _revision != 2000)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !reader.readBytes(unknown1) || !this->vector.load(reader))
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readBytes(unknown1) || !this->vector.load(reader))
return kDataReadErrorReadFailed;
return kDataReadErrorNone;
@@ -1937,7 +1927,7 @@ DataReadErrorCode PointVariableModifier::load(DataReader &reader) {
if (_revision != 1000)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !reader.readBytes(unknown5) || !value.load(reader))
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readBytes(unknown5) || !value.load(reader))
return kDataReadErrorReadFailed;
return kDataReadErrorNone;
@@ -1950,7 +1940,7 @@ DataReadErrorCode FloatingPointVariableModifier::load(DataReader &reader) {
if (_revision != 1000)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !reader.readBytes(unknown1) || !reader.readPlatformFloat(value))
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readBytes(unknown1) || !reader.readPlatformFloat(value))
return kDataReadErrorReadFailed;
return kDataReadErrorNone;
@@ -1963,7 +1953,7 @@ DataReadErrorCode StringVariableModifier::load(DataReader &reader) {
if (_revision != 1000)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !reader.readU32(lengthOfString) || !reader.readBytes(unknown1) || !reader.readTerminatedStr(value, lengthOfString))
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readU32(lengthOfString) || !reader.readBytes(unknown1) || !reader.readTerminatedStr(value, lengthOfString))
return kDataReadErrorReadFailed;
return kDataReadErrorNone;
@@ -1976,7 +1966,7 @@ DataReadErrorCode ObjectReferenceVariableModifierV1::load(DataReader &reader) {
if (_revision != 1000)
return kDataReadErrorUnsupportedRevision;
- if (!modHeader.load(reader) || !reader.readU32(unknown1) || !setToSourcesParentWhen.load(reader))
+ if (!modHeader.load(reader, _revision >= 2000) || !reader.readU32(unknown1) || !setToSourcesParentWhen.load(reader))
return kDataReadErrorReadFailed;
return kDataReadErrorNone;
@@ -2007,7 +1997,7 @@ DataReadErrorCode PlugInModifier::load(DataReader &reader) {
modifierName[16] = 0;
subObjectSize = codedSize;
- if (reader.getProjectFormat() == kProjectFormatWindows) {
+ if (reader.getDataFormat() == kDataFormatWindows) {
// This makes no sense but it's how it's stored...
if (subObjectSize < lengthOfName * 256u)
return kDataReadErrorReadFailed;
@@ -2049,10 +2039,10 @@ DataReadErrorCode ColorTableAsset::load(DataReader &reader) {
if (!reader.readU32(persistFlags) || !reader.readU32(sizeIncludingTag))
return kDataReadErrorReadFailed;
- if (reader.getProjectFormat() == Data::kProjectFormatMacintosh) {
+ if (reader.getDataFormat() == Data::kDataFormatMacintosh) {
if (sizeIncludingTag != 0x0836)
return kDataReadErrorUnrecognized;
- } else if (reader.getProjectFormat() == Data::kProjectFormatWindows) {
+ } else if (reader.getDataFormat() == Data::kDataFormatWindows) {
if (sizeIncludingTag != 0x0428)
return kDataReadErrorUnrecognized;
} else
@@ -2062,7 +2052,7 @@ DataReadErrorCode ColorTableAsset::load(DataReader &reader) {
return kDataReadErrorReadFailed;
size_t numColors = 256;
- if (reader.getProjectFormat() == Data::kProjectFormatMacintosh) {
+ if (reader.getDataFormat() == Data::kDataFormatMacintosh) {
if (!reader.skip(20))
return kDataReadErrorReadFailed;
@@ -2082,7 +2072,7 @@ DataReadErrorCode ColorTableAsset::load(DataReader &reader) {
cdef.green = (rgb[2] << 8) | rgb[3];
cdef.blue = (rgb[4] << 8) | rgb[5];
}
- } else if (reader.getProjectFormat() == Data::kProjectFormatWindows) {
+ } else if (reader.getDataFormat() == Data::kDataFormatWindows) {
if (!reader.skip(14))
return kDataReadErrorReadFailed;
@@ -2110,7 +2100,7 @@ MovieAsset::MovieAsset()
}
DataReadErrorCode MovieAsset::load(DataReader &reader) {
- if (_revision != 0)
+ if (_revision != 0 && _revision != 1)
return kDataReadErrorUnsupportedRevision;
haveMacPart = false;
@@ -2120,16 +2110,28 @@ DataReadErrorCode MovieAsset::load(DataReader &reader) {
|| !reader.readU32(assetID) || !reader.readBytes(unknown1_1) || !reader.readU16(extFileNameLength))
return kDataReadErrorReadFailed;
- if (reader.getProjectFormat() == Data::kProjectFormatMacintosh) {
+ if (reader.getDataFormat() == Data::kDataFormatMacintosh) {
haveMacPart = true;
if (!reader.readBytes(platform.mac.unknown5_1) || !reader.readU32(movieDataSize) || !reader.readBytes(platform.mac.unknown6) || !reader.readU32(moovAtomPos))
return kDataReadErrorReadFailed;
- } else if (reader.getProjectFormat() == Data::kProjectFormatWindows) {
+
+ if (_revision >= 1) {
+ if (!reader.readBytes(platform.mac.unknown8))
+ return kDataReadErrorReadFailed;
+ } else {
+ for (uint8 &b : platform.mac.unknown6)
+ b = 0;
+ }
+
+ } else if (reader.getDataFormat() == Data::kDataFormatWindows) {
haveWinPart = true;
if (!reader.readBytes(platform.win.unknown3_1) || !reader.readU32(movieDataSize) || !reader.readBytes(platform.win.unknown4) || !reader.readU32(moovAtomPos) || !reader.readBytes(platform.win.unknown7))
return kDataReadErrorReadFailed;
+
+ if (_revision != 0)
+ return kDataReadErrorReadFailed;
} else
return kDataReadErrorReadFailed;
@@ -2183,7 +2185,7 @@ DataReadErrorCode AudioAsset::load(DataReader &reader) {
haveWinPart = false;
isBigEndian = false;
- if (reader.getProjectFormat() == Data::ProjectFormat::kProjectFormatMacintosh) {
+ if (reader.getDataFormat() == Data::kDataFormatMacintosh) {
haveMacPart = true;
isBigEndian = true;
@@ -2192,7 +2194,7 @@ DataReadErrorCode AudioAsset::load(DataReader &reader) {
|| !reader.readBytes(codedDuration) || !reader.readBytes(platform.mac.unknown8)
|| !reader.readU16(sampleRate2))
return kDataReadErrorReadFailed;
- } else if (reader.getProjectFormat() == Data::ProjectFormat::kProjectFormatWindows) {
+ } else if (reader.getDataFormat() == Data::kDataFormatWindows) {
haveWinPart = true;
if (!reader.readU16(sampleRate1) || !reader.readU8(bitsPerSample) || !reader.readBytes(platform.win.unknown9)
@@ -2242,11 +2244,11 @@ DataReadErrorCode ImageAsset::load(DataReader &reader) {
haveWinPart = false;
haveMacPart = false;
- if (reader.getProjectFormat() == kProjectFormatMacintosh) {
+ if (reader.getDataFormat() == kDataFormatMacintosh) {
haveMacPart = true;
if (!reader.readBytes(platform.mac.unknown7))
return kDataReadErrorReadFailed;
- } else if (reader.getProjectFormat() == kProjectFormatWindows) {
+ } else if (reader.getDataFormat() == kDataFormatWindows) {
haveWinPart = true;
if (!reader.readBytes(platform.win.unknown8))
return kDataReadErrorReadFailed;
@@ -2293,12 +2295,12 @@ DataReadErrorCode MToonAsset::load(DataReader &reader) {
haveMacPart = false;
haveWinPart = false;
- if (reader.getProjectFormat() == kProjectFormatMacintosh) {
+ if (reader.getDataFormat() == kDataFormatMacintosh) {
haveMacPart = true;
if (!reader.readBytes(platform.mac.unknown10))
return kDataReadErrorReadFailed;
- } else if (reader.getProjectFormat() == kProjectFormatWindows) {
+ } else if (reader.getDataFormat() == kDataFormatWindows) {
haveWinPart = true;
if (!reader.readBytes(platform.win.unknown11))
@@ -2328,10 +2330,10 @@ DataReadErrorCode MToonAsset::load(DataReader &reader) {
|| !reader.readU16(frame.bitsPerPixel) || !reader.readU32(frame.unknown16) || !reader.readU16(frame.decompressedBytesPerRow))
return kDataReadErrorReadFailed;
- if (reader.getProjectFormat() == kProjectFormatMacintosh) {
+ if (reader.getDataFormat() == kDataFormatMacintosh) {
if (!reader.readBytes(frame.platform.mac.unknown17))
return kDataReadErrorReadFailed;
- } else if (reader.getProjectFormat() == kProjectFormatWindows) {
+ } else if (reader.getDataFormat() == kDataFormatWindows) {
if (!reader.readBytes(frame.platform.win.unknown18))
return kDataReadErrorReadFailed;
} else
@@ -2391,12 +2393,12 @@ DataReadErrorCode TextAsset::load(DataReader &reader) {
haveMacPart = false;
haveWinPart = false;
- if (reader.getProjectFormat() == kProjectFormatMacintosh) {
+ if (reader.getDataFormat() == kDataFormatMacintosh) {
haveMacPart = true;
isBottomUp = false;
if (!reader.readBytes(platform.mac.unknown3))
return kDataReadErrorReadFailed;
- } else if (reader.getProjectFormat() == kProjectFormatWindows) {
+ } else if (reader.getDataFormat() == kDataFormatWindows) {
haveWinPart = true;
isBottomUp = true;
if (!reader.readBytes(platform.win.unknown4))
@@ -2414,7 +2416,7 @@ DataReadErrorCode TextAsset::load(DataReader &reader) {
if (!reader.readNonTerminatedStr(text, textSize))
return kDataReadErrorReadFailed;
- if (reader.getProjectFormat() == kProjectFormatMacintosh) {
+ if (reader.getDataFormat() == kDataFormatMacintosh) {
uint16 numFormattingSpans;
if (!reader.readU16(numFormattingSpans))
return kDataReadErrorReadFailed;
diff --git a/engines/mtropolis/data.h b/engines/mtropolis/data.h
index 7f68e638701..85441f80547 100644
--- a/engines/mtropolis/data.h
+++ b/engines/mtropolis/data.h
@@ -23,6 +23,7 @@
#define MTROPOLIS_DATA_H
#include "common/array.h"
+#include "common/endian.h"
#include "common/error.h"
#include "common/hashmap.h"
#include "common/hash-str.h"
@@ -46,6 +47,21 @@ namespace Data {
struct PlugInModifier;
struct PlugInModifierData;
+// Project format and data format are 2 separate things.
+//
+// A cross-platform project can be booted in either Mac or Win mode and contains
+// separate scene streams for Mac and Win. Which one is loaded depends on what the
+// game platform is loaded as.
+//
+// The following table describes the behavior:
+//
+// Project type | ProjectFormat | Catalog and asset data format | Scene data format |
+// ------------------------------------------------------------------------------------------|
+// Mac | Macintosh | Macintosh | Macintosh |
+// Win | Windows | Windows | Windows |
+// Cross-Platform as Mac | Neutral | Windows | Macintosh |
+// Cross-Platform as Win | Neutral | Windows | Windows |
+
enum ProjectFormat {
kProjectFormatUnknown,
@@ -54,10 +70,11 @@ enum ProjectFormat {
kProjectFormatNeutral,
};
-enum ProjectEngineVersion {
- kProjectEngineVersionUnknown,
- kProjectEngineVersion1,
- kProjectEngineVersion2,
+enum DataFormat {
+ kDataFormatUnknown,
+
+ kDataFormatMacintosh,
+ kDataFormatWindows,
};
enum DataReadErrorCode {
@@ -179,9 +196,224 @@ namespace StructuralFlags {
};
} // End of namespace StructuralFlags
+template<class T>
+struct SimpleDataIO {
+ static const uint kMaxSize = sizeof(T);
+
+ static uint computeSize(DataFormat dataFormat);
+
+ static void encode(DataFormat dataFormat, byte *data, const T &value);
+ static void decode(DataFormat dataFormat, const byte *data, T &value);
+};
+
+template<class T>
+uint SimpleDataIO<T>::computeSize(DataFormat dataFormat) {
+ return sizeof(T);
+}
+
+template<class T>
+void SimpleDataIO<T>::encode(DataFormat dataFormat, byte *data, const T &value) {
+ const byte *valueBytes = reinterpret_cast<const byte *>(&value);
+ byte *dataBytes = reinterpret_cast<byte *>(data);
+
+ const bool isTargetLE = (dataFormat != kDataFormatMacintosh);
+#ifdef SCUMM_LITTLE_ENDIAN
+ const bool isSystemLE = true;
+#endif
+#ifdef SCUMM_BIG_ENDIAN
+ const bool isSystemLE = false;
+#endif
+
+ const bool requiresSwap = (isSystemLE != isTargetLE);
+
+ byte temp[sizeof(T)];
+
+ if (requiresSwap) {
+ for (uint i = 0; i < sizeof(T); i++)
+ temp[i] = valueBytes[sizeof(T) - 1 - i];
+ } else {
+ for (uint i = 0; i < sizeof(T); i++)
+ temp[i] = valueBytes[i];
+ }
+
+ for (uint i = 0; i < sizeof(T); i++)
+ dataBytes[i] = temp[i];
+}
+
+template<class T>
+void SimpleDataIO<T>::decode(DataFormat dataFormat, const byte *data, T &value) {
+ byte *valueBytes = reinterpret_cast<byte *>(&value);
+ const byte *dataBytes = reinterpret_cast<const byte *>(data);
+
+ const bool isTargetLE = (dataFormat != kDataFormatMacintosh);
+#ifdef SCUMM_LITTLE_ENDIAN
+ const bool isSystemLE = true;
+#endif
+#ifdef SCUMM_BIG_ENDIAN
+ const bool isSystemLE = false;
+#endif
+
+ const bool requiresSwap = (isSystemLE != isTargetLE);
+
+ byte temp[sizeof(T)];
+
+ if (requiresSwap) {
+ for (uint i = 0; i < sizeof(T); i++)
+ temp[i] = dataBytes[sizeof(T) - 1 - i];
+ } else {
+ for (uint i = 0; i < sizeof(T); i++)
+ temp[i] = dataBytes[i];
+ }
+
+ for (uint i = 0; i < sizeof(T); i++)
+ valueBytes[i] = temp[i];
+}
+
+template<class T>
+void SimpleDataIO<T>::decode(DataFormat dataFormat, const byte *data, T &value);
+
+template<class T>
+struct DataIO {
+};
+
+template<>
+struct DataIO<uint8> : public SimpleDataIO<uint8> {
+};
+
+template<>
+struct DataIO<uint16> : public SimpleDataIO<uint16> {
+};
+template<>
+struct DataIO<uint32> : public SimpleDataIO<uint32> {
+};
+template<>
+struct DataIO<uint64> : public SimpleDataIO<uint64> {
+};
+
+template<>
+struct DataIO<int8> : public SimpleDataIO<int8> {
+};
+
+template<>
+struct DataIO<int16> : public SimpleDataIO<int16> {
+};
+template<>
+struct DataIO<int32> : public SimpleDataIO<int32> {
+};
+template<>
+struct DataIO<int64> : public SimpleDataIO<int64> {
+};
+
+template<>
+struct DataIO<char> : public SimpleDataIO<char> {
+};
+
+template<>
+struct DataIO<float> : public SimpleDataIO<float> {
+};
+
+template<>
+struct DataIO<double> : public SimpleDataIO<double> {
+};
+
+template<>
+struct DataIO<Common::XPFloat> {
+ static const uint kMaxSize = 10;
+
+ static uint computeSize(DataFormat dataFormat);
+
+ static void encode(DataFormat dataFormat, byte *data, const Common::XPFloat &value);
+ static void decode(DataFormat dataFormat, const byte *data, Common::XPFloat &value);
+};
+
+template<class T, class... TMore>
+struct DataMultipleIO;
+
+template<class T>
+struct DataMultipleIO<T> {
+ static const uint kMaxSize = DataIO<T>::kMaxSize;
+
+ static uint computeSize(DataFormat dataFormat);
+
+ static void encode(DataFormat dataFormat, byte *data, const T &value);
+ static void decode(DataFormat dataFormat, const byte *data, T &value);
+};
+
+template<class T>
+uint DataMultipleIO<T>::computeSize(DataFormat dataFormat) {
+ return DataIO<T>::computeSize(dataFormat);
+}
+
+template<class T>
+void DataMultipleIO<T>::encode(DataFormat dataFormat, byte *data, const T &value) {
+ return DataIO<T>::encode(dataFormat, data, value);
+}
+
+template<class T>
+void DataMultipleIO<T>::decode(DataFormat dataFormat, const byte *data, T &value) {
+ return DataIO<T>::decode(dataFormat, data, value);
+}
+
+template<class T, uint TSize>
+struct DataMultipleIO<T[TSize]> {
+ static const uint kMaxSize = DataIO<T>::kMaxSize * TSize;
+
+ static uint computeSize(DataFormat dataFormat);
+
+ static void encode(DataFormat dataFormat, byte *data, const T (&value)[TSize]);
+ static void decode(DataFormat dataFormat, const byte *data, T(&value)[TSize]);
+};
+
+template<class T, uint TSize>
+uint DataMultipleIO<T[TSize]>::computeSize(DataFormat dataFormat) {
+ return DataMultipleIO<T>::computeSize(dataFormat) * TSize;
+}
+
+template<class T, uint TSize>
+void DataMultipleIO<T[TSize]>::encode(DataFormat dataFormat, byte *data, const T(&value)[TSize]) {
+ const uint elementSize = DataIO<T>::computeSize(dataFormat);
+ for (uint i = 0; i < TSize; i++)
+ DataMultipleIO<T>::encode(dataFormat, data + elementSize * i, value[i]);
+}
+
+template<class T, uint TSize>
+void DataMultipleIO<T[TSize]>::decode(DataFormat dataFormat, const byte *data, T(&value)[TSize]) {
+ const uint elementSize = DataIO<T>::computeSize(dataFormat);
+ for (uint i = 0; i < TSize; i++)
+ DataMultipleIO<T>::decode(dataFormat, data + elementSize * i, value[i]);
+}
+
+template<class T, class... TMore>
+struct DataMultipleIO {
+ static const uint kMaxSize = DataIO<T>::kMaxSize + DataMultipleIO<TMore...>::kMaxSize;
+
+ static uint computeSize(DataFormat dataFormat);
+
+ static void encode(DataFormat dataFormat, byte *data, const T &firstValue, const TMore &...moreValues);
+ static void decode(DataFormat dataFormat, const byte *data, T &firstValue, TMore &...moreValues);
+};
+
+template<class T, class... TMore>
+uint DataMultipleIO<T, TMore...>::computeSize(DataFormat dataFormat) {
+ return DataMultipleIO<T>::computeSize(dataFormat) + DataMultipleIO<TMore...>::computeSize(dataFormat);
+}
+
+template<class T, class... TMore>
+void DataMultipleIO<T, TMore...>::encode(DataFormat dataFormat, byte *data, const T &firstValue, const TMore &...moreValues) {
+ DataMultipleIO<T>::encode(dataFormat, data, firstValue);
+ DataMultipleIO<TMore...>::encode(dataFormat, data + DataMultipleIO<T>::computeSize(dataFormat), moreValues...);
+}
+
+template<class T, class... TMore>
+void DataMultipleIO<T, TMore...>::decode(DataFormat dataFormat, const byte *data, T &firstValue, TMore &...moreValues) {
+ DataMultipleIO<T>::decode(dataFormat, data, firstValue);
+ DataMultipleIO<TMore...>::decode(dataFormat, data + DataMultipleIO<T>::computeSize(dataFormat), moreValues...);
+}
+
+
class DataReader {
public:
- DataReader(int64 globalPosition, Common::SeekableReadStreamEndian &stream, ProjectFormat projectFormat, ProjectEngineVersion projectEngineVersion);
+ DataReader(int64 globalPosition, Common::SeekableReadStream &stream, DataFormat dataFormat);
bool readU8(uint8 &value);
bool readU16(uint16 &value);
@@ -195,6 +427,9 @@ public:
bool readF64(double &value);
bool readPlatformFloat(Common::XPFloat &value);
+ template<class... T>
+ bool readMultiple(T &...values);
+
bool read(void *dest, size_t size);
// Reads a terminated string where "length" is the number of characters including a null terminator
@@ -214,24 +449,32 @@ public:
int64 tell() const;
inline int64 tellGlobal() const { return _globalPosition + tell(); }
- ProjectFormat getProjectFormat() const;
- ProjectEngineVersion getProjectEngineVersion() const;
- bool isBigEndian() const;
- bool isV2Project() const;
+ DataFormat getDataFormat() const;
void setPermitDamagedStrings(bool permit);
private:
bool checkErrorAndReset();
- Common::SeekableReadStreamEndian &_stream;
- ProjectFormat _projectFormat;
- ProjectEngineVersion _projectEngineVersion;
+ Common::SeekableReadStream &_stream;
+ DataFormat _dataFormat;
int64 _globalPosition;
bool _permitDamagedStrings;
};
+template<class... T>
+bool DataReader::readMultiple(T &...values) {
+ byte buffer[DataMultipleIO<T...>::kMaxSize];
+ const uint actualSize = DataMultipleIO<T...>::computeSize(_dataFormat);
+
+ if (!read(buffer, actualSize))
+ return false;
+
+ DataMultipleIO<T...>::decode(_dataFormat, buffer, values...);
+ return true;
+}
+
struct Rect {
Rect();
@@ -848,8 +1091,10 @@ public:
char streamType[25];
uint16 segmentIndexPlusOne;
- uint32 size;
- uint32 pos;
+ uint32 winSize;
+ uint32 winPos;
+ uint32 macSize;
+ uint32 macPos;
};
struct SegmentDesc {
@@ -950,9 +1195,7 @@ struct MiniscriptProgram {
Common::Array<LocalRef> localRefs;
Common::Array<Attribute> attributes;
- ProjectFormat projectFormat;
- ProjectEngineVersion projectEngineVersion;
- bool isBigEndian;
+ DataFormat dataFormat;
bool load(DataReader &reader);
};
@@ -972,7 +1215,7 @@ struct TypicalModifierHeader {
Common::String name;
- bool load(DataReader &reader);
+ bool load(DataReader &reader, bool isV2);
};
struct MiniscriptModifier : public DataObject {
@@ -1104,7 +1347,7 @@ struct AliasModifier : public DataObject {
uint16 aliasIndexPlusOne;
uint32 unknown1;
uint32 unknown2;
- uint16 lengthOfName;
+ uint32 lengthOfName;
uint32 guid;
Point editorLayoutPosition;
@@ -1842,6 +2085,7 @@ struct MovieAsset : public DataObject {
struct MacPart {
uint8 unknown5_1[66];
uint8 unknown6[12];
+ uint8 unknown8[4];
};
struct WinPart {
diff --git a/engines/mtropolis/detection.cpp b/engines/mtropolis/detection.cpp
index fb6f0113f5b..c4cdafb1624 100644
--- a/engines/mtropolis/detection.cpp
+++ b/engines/mtropolis/detection.cpp
@@ -23,6 +23,7 @@
#include "engines/advancedDetector.h"
#include "common/config-manager.h"
+#include "common/file.h"
#include "mtropolis/detection.h"
@@ -35,6 +36,7 @@ static const PlainGameDescriptor mTropolisGames[] = {
{"spqr", "SPQR: The Empire's Darkest Hour"},
{"sttgs", "Star Trek: The Game Show"},
{"unit", "Unit: Re-Booted"},
+ {"mtropolis", "mTropolis Title"},
{nullptr, nullptr}
};
@@ -71,6 +73,94 @@ public:
const char *getOriginalCopyright() const override {
return "mTropolis (C) mFactory/Quark";
}
+
+ ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist, ADDetectedGameExtraInfo **extra) const override;
+
+ static MTropolis::MTropolisGameDescription _globalFallbackDesc;
+};
+
+ADDetectedGame MTropolisMetaEngineDetection::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist, ADDetectedGameExtraInfo **outExtra) const {
+ const char *winBootFileName = MTROPOLIS_WIN_BOOT_SCRIPT_NAME;
+ const char *macBootFileName = MTROPOLIS_MAC_BOOT_SCRIPT_NAME;
+
+ FileMap::const_iterator macBootScriptIt = allFiles.find(macBootFileName);
+ FileMap::const_iterator winBootScriptIt = allFiles.find(winBootFileName);
+
+ bool foundMac = (macBootScriptIt != allFiles.end());
+ bool foundWin = (winBootScriptIt != allFiles.end());
+
+ if (foundMac && foundWin) {
+ warning("Found both %s and %s, need exactly one or the other to boot a mTropolis project", winBootFileName, macBootFileName);
+ return ADDetectedGame();
+ }
+
+ if (!foundMac && !foundWin)
+ return ADDetectedGame();
+
+ MTropolis::MTropolisGameDescription *desc = &_globalFallbackDesc;
+
+ const char *scriptName = nullptr;
+ if (foundWin) {
+ desc->desc.platform = Common::kPlatformWindows;
+ scriptName = winBootFileName;
+ } else if (foundMac) {
+ desc->desc.platform = Common::kPlatformMacintosh;
+ scriptName = macBootFileName;
+ }
+
+ if (outExtra) {
+ const Common::FSNode *bootScriptFile = nullptr;
+
+ if (foundMac)
+ bootScriptFile = &macBootScriptIt->_value;
+ if (foundWin)
+ bootScriptFile = &winBootScriptIt->_value;
+
+ Common::File f;
+ if (bootScriptFile && f.open(*bootScriptFile)) {
+ Common::String targetID = "mtropolis-fallback";
+ Common::String gameName = foundWin ? "Windows mTropolis Title" : "Macintosh mTropolis Title";
+
+ Common::String lineStr;
+ while (!f.err() && !f.eos()) {
+ lineStr = f.readString('\n');
+ lineStr.trim();
+
+ const char *targetIDPrefix = "//targetID:";
+ const char *gameNamePrefix = "//gameName:";
+
+ if (lineStr.hasPrefix(targetIDPrefix))
+ targetID = lineStr.substr(strlen(targetIDPrefix));
+ else if (lineStr.hasPrefix(gameNamePrefix))
+ gameName = lineStr.substr(strlen(gameNamePrefix));
+ }
+
+ ADDetectedGameExtraInfo *extraInfo = new ADDetectedGameExtraInfo();
+ extraInfo->gameName = gameName;
+ extraInfo->targetID = targetID;
+
+ *outExtra = extraInfo;
+ }
+ }
+
+ return ADDetectedGame(&desc->desc);
+}
+
+MTropolis::MTropolisGameDescription MTropolisMetaEngineDetection::_globalFallbackDesc = {
+ {
+ "mtropolis",
+ "",
+ {
+ AD_LISTEND
+ },
+ Common::UNK_LANG,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ GUIO0()
+ },
+ MTropolis::GID_GENERIC,
+ 0,
+ MTropolis::MTBOOT_USE_BOOT_SCRIPT,
};
REGISTER_PLUGIN_STATIC(MTROPOLIS_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, MTropolisMetaEngineDetection);
diff --git a/engines/mtropolis/detection.h b/engines/mtropolis/detection.h
index 9818a3ea0b7..5cdb80a22de 100644
--- a/engines/mtropolis/detection.h
+++ b/engines/mtropolis/detection.h
@@ -24,6 +24,9 @@
#include "engines/advancedDetector.h"
+#define MTROPOLIS_WIN_BOOT_SCRIPT_NAME "mtropolis_boot_win.txt"
+#define MTROPOLIS_MAC_BOOT_SCRIPT_NAME "mtropolis_boot_mac.txt"
+
namespace MTropolis {
enum MTropolisGameID {
@@ -36,6 +39,8 @@ enum MTropolisGameID {
GID_SPQR = 6,
GID_STTGS = 7,
GID_UNIT = 8,
+
+ GID_GENERIC = 9,
};
// Boot IDs - These can be shared across different variants if the file list and other properties are identical.
@@ -43,6 +48,8 @@ enum MTropolisGameID {
enum MTropolisGameBootID {
MTBOOT_INVALID = 0,
+ MTBOOT_USE_BOOT_SCRIPT,
+
MTBOOT_OBSIDIAN_RETAIL_MAC_EN,
MTBOOT_OBSIDIAN_RETAIL_MAC_JP,
MTBOOT_OBSIDIAN_RETAIL_WIN_EN,
diff --git a/engines/mtropolis/miniscript.cpp b/engines/mtropolis/miniscript.cpp
index bafba72ef7d..6afb5432b7e 100644
--- a/engines/mtropolis/miniscript.cpp
+++ b/engines/mtropolis/miniscript.cpp
@@ -379,8 +379,8 @@ bool MiniscriptParser::parse(const Data::MiniscriptProgram &program, Common::Sha
attributes[i].name = program.attributes[i].name;
}
- Common::MemoryReadStreamEndian stream(&program.bytecode[0], program.bytecode.size(), program.isBigEndian);
- Data::DataReader reader(0, stream, program.projectFormat, program.projectEngineVersion);
+ Common::MemoryReadStream stream(&program.bytecode[0], program.bytecode.size());
+ Data::DataReader reader(0, stream, program.dataFormat);
Common::Array<InstructionData> rawInstructions;
rawInstructions.resize(program.numOfInstructions);
@@ -449,8 +449,8 @@ bool MiniscriptParser::parse(const Data::MiniscriptProgram &program, Common::Sha
if (rawInstruction.contents.size() != 0)
dataLoc = &rawInstruction.contents[0];
- Common::MemoryReadStreamEndian instrContentsStream(static_cast<const byte *>(dataLoc), rawInstruction.contents.size(), reader.isBigEndian());
- Data::DataReader instrContentsReader(0, instrContentsStream, reader.getProjectFormat(), reader.getProjectEngineVersion());
+ Common::MemoryReadStream instrContentsStream(static_cast<const byte *>(dataLoc), rawInstruction.contents.size());
+ Data::DataReader instrContentsReader(0, instrContentsStream, reader.getDataFormat());
if (!rawInstruction.instrFactory->create(&programData[baseOffset + rawInstruction.pdPosition], rawInstruction.flags, instrContentsReader, miniscriptInstructions[i], parserFeedback)) {
// Destroy any already-created instructions
diff --git a/engines/mtropolis/module.mk b/engines/mtropolis/module.mk
index 1d45e895c6b..21f62aec292 100644
--- a/engines/mtropolis/module.mk
+++ b/engines/mtropolis/module.mk
@@ -28,6 +28,7 @@ MODULE_OBJS = \
runtime.o \
saveload.o \
subtitles.o \
+ vfs.o \
vthread.o
# This module can be built as a plugin
diff --git a/engines/mtropolis/mtropolis.cpp b/engines/mtropolis/mtropolis.cpp
index c9c20600d26..6c34285f0c2 100644
--- a/engines/mtropolis/mtropolis.cpp
+++ b/engines/mtropolis/mtropolis.cpp
@@ -142,50 +142,61 @@ Common::Error MTropolisEngine::run() {
subRenderer.reset();
- if (_gameDescription->gameID == GID_OBSIDIAN) {
- preferredWidth = 640;
- preferredHeight = 480;
- preferredColorDepthMode = kColorDepthMode16Bit;
- enhancedColorDepthMode = kColorDepthMode32Bit;
-
- HackSuites::addObsidianQuirks(*_gameDescription, _runtime->getHacks());
- HackSuites::addObsidianBugFixes(*_gameDescription, _runtime->getHacks());
- HackSuites::addObsidianSaveMechanism(*_gameDescription, _runtime->getHacks());
-
- if (ConfMan.getBool("mtropolis_mod_auto_save_at_checkpoints"))
- HackSuites::addObsidianAutoSaves(*_gameDescription, _runtime->getHacks(), this);
-
- if (ConfMan.getBool("mtropolis_mod_obsidian_widescreen")) {
- _runtime->getHacks().reportDisplaySize = Common::Point(640, 480);
-
- preferredHeight = 360;
- HackSuites::addObsidianImprovedWidescreen(*_gameDescription, _runtime->getHacks());
- }
- } else if (_gameDescription->gameID == GID_MTI) {
- preferredWidth = 640;
- preferredHeight = 480;
- preferredColorDepthMode = kColorDepthMode8Bit;
- enhancedColorDepthMode = kColorDepthMode32Bit;
-
- HackSuites::addMTIQuirks(*_gameDescription, _runtime->getHacks());
- } else if (_gameDescription->gameID == GID_SPQR) {
- preferredWidth = 640;
- preferredHeight = 480;
+ // Get project boot configuration
+ BootConfiguration bootConfig = bootProject(*_gameDescription);
+
+ _runtime->queueProject(bootConfig._projectDesc);
+
+ preferredWidth = bootConfig._width;
+ preferredHeight = bootConfig._height;
+
+ switch (bootConfig._bitDepth) {
+ case 1:
+ preferredColorDepthMode = kColorDepthMode1Bit;
+ break;
+ case 2:
+ preferredColorDepthMode = kColorDepthMode2Bit;
+ break;
+ case 4:
+ preferredColorDepthMode = kColorDepthMode4Bit;
+ break;
+ case 8:
preferredColorDepthMode = kColorDepthMode8Bit;
- enhancedColorDepthMode = kColorDepthMode32Bit;
- } else if (_gameDescription->gameID == GID_UNIT) {
- preferredWidth = 640;
- preferredHeight = 480;
+ break;
+ case 16:
+ preferredColorDepthMode = kColorDepthMode16Bit;
+ break;
+ case 32:
preferredColorDepthMode = kColorDepthMode32Bit;
- enhancedColorDepthMode = kColorDepthMode32Bit;
-
- Palette pal;
- pal.initDefaultPalette(2);
- _runtime->setGlobalPalette(pal);
+ break;
+ default:
+ error("Unsupported color depth mode");
+ break;
}
- if (ConfMan.getBool("mtropolis_mod_minimum_transition_duration"))
- _runtime->getHacks().minTransitionDuration = 75;
+ switch (bootConfig._enhancedBitDepth) {
+ case 1:
+ enhancedColorDepthMode = kColorDepthMode1Bit;
+ break;
+ case 2:
+ enhancedColorDepthMode = kColorDepthMode2Bit;
+ break;
+ case 4:
+ enhancedColorDepthMode = kColorDepthMode4Bit;
+ break;
+ case 8:
+ enhancedColorDepthMode = kColorDepthMode8Bit;
+ break;
+ case 16:
+ enhancedColorDepthMode = kColorDepthMode16Bit;
+ break;
+ case 32:
+ enhancedColorDepthMode = kColorDepthMode32Bit;
+ break;
+ default:
+ error("Unsupported color depth mode");
+ break;
+ }
// Figure out pixel formats
Graphics::PixelFormat modePixelFormats[kColorDepthModeCount];
@@ -283,13 +294,6 @@ Common::Error MTropolisEngine::run() {
initGraphics(preferredWidth, preferredHeight, &modePixelFormats[selectedMode]);
-
-
- // Start the project
- Common::SharedPtr<ProjectDescription> projectDesc = bootProject(*_gameDescription);
- _runtime->queueProject(projectDesc);
-
-
#ifdef MTROPOLIS_DEBUG_ENABLE
if (ConfMan.getBool("mtropolis_debug_at_start")) {
_runtime->debugSetEnabled(true);
@@ -299,6 +303,37 @@ Common::Error MTropolisEngine::run() {
}
#endif
+ // Done reading boot configuration
+ bootConfig = BootConfiguration();
+
+ // Apply mods
+ if (ConfMan.getBool("mtropolis_mod_minimum_transition_duration"))
+ _runtime->getHacks().minTransitionDuration = 75;
+
+ // Apply game-specific mods and hacks
+ if (_gameDescription->gameID == GID_OBSIDIAN) {
+ HackSuites::addObsidianQuirks(*_gameDescription, _runtime->getHacks());
+ HackSuites::addObsidianBugFixes(*_gameDescription, _runtime->getHacks());
+ HackSuites::addObsidianSaveMechanism(*_gameDescription, _runtime->getHacks());
+
+ if (ConfMan.getBool("mtropolis_mod_auto_save_at_checkpoints"))
+ HackSuites::addObsidianAutoSaves(*_gameDescription, _runtime->getHacks(), this);
+
+ if (ConfMan.getBool("mtropolis_mod_obsidian_widescreen")) {
+ _runtime->getHacks().reportDisplaySize = Common::Point(640, 480);
+
+ preferredHeight = 360;
+ HackSuites::addObsidianImprovedWidescreen(*_gameDescription, _runtime->getHacks());
+ }
+ } else if (_gameDescription->gameID == GID_MTI) {
+ HackSuites::addMTIQuirks(*_gameDescription, _runtime->getHacks());
+ } else if (_gameDescription->gameID == GID_UNIT) {
+ Palette pal;
+ pal.initDefaultPalette(2);
+ _runtime->setGlobalPalette(pal);
+ }
+
+
while (!shouldQuit()) {
handleEvents();
diff --git a/engines/mtropolis/runtime.cpp b/engines/mtropolis/runtime.cpp
index e994b1e26e5..3663fd97cdf 100644
--- a/engines/mtropolis/runtime.cpp
+++ b/engines/mtropolis/runtime.cpp
@@ -21,6 +21,7 @@
#include "common/debug.h"
#include "common/file.h"
+#include "common/macresman.h"
#include "common/random.h"
#include "common/substream.h"
#include "common/system.h"
@@ -2501,7 +2502,8 @@ Common::SharedPtr<CursorGraphic> CursorGraphicCollection::getGraphicByID(uint32
return nullptr;
}
-ProjectDescription::ProjectDescription(ProjectPlatform platform) : _language(Common::EN_ANY), _platform(platform) {
+ProjectDescription::ProjectDescription(ProjectPlatform platform, ProjectMajorVersion majorVersion, Common::Archive *rootArchive, const Common::Path &projectRootDir)
+ : _language(Common::EN_ANY), _platform(platform), _rootArchive(rootArchive), _projectRootDir(projectRootDir), _majorVersion(majorVersion) {
}
ProjectDescription::~ProjectDescription() {
@@ -2564,11 +2566,23 @@ ProjectPlatform ProjectDescription::getPlatform() const {
return _platform;
}
+ProjectMajorVersion ProjectDescription::getMajorVersion() const {
+ return _majorVersion;
+}
+
+Common::Archive *ProjectDescription::getRootArchive() const {
+ return _rootArchive;
+}
+
+const Common::Path &ProjectDescription::getProjectRootDir() const {
+ return _projectRootDir;
+}
+
const SubtitleTables &ProjectDescription::getSubtitles() const {
return _subtitles;
}
-void ProjectDescription::getSubtitles(const SubtitleTables &subs) {
+void ProjectDescription::setSubtitles(const SubtitleTables &subs) {
_subtitles = subs;
}
@@ -2582,6 +2596,10 @@ void SimpleModifierContainer::appendModifier(const Common::SharedPtr<Modifier> &
modifier->setParent(nullptr);
}
+void SimpleModifierContainer::clear() {
+ _modifiers.clear();
+}
+
RuntimeObject::RuntimeObject() : _guid(0), _runtimeGUID(0) {
}
@@ -4334,7 +4352,7 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, ISaveUIProvider *saveProv
Runtime::~Runtime() {
// Clear the project first, which should detach any references to other things
- _project.reset();
+ unloadProject();
_subtitleRenderer.reset();
}
@@ -6943,15 +6961,37 @@ Project::AssetDesc::AssetDesc() : typeCode(0), id(0), streamID(0), filePosition(
}
Project::Project(Runtime *runtime)
- : Structural(runtime), _projectFormat(Data::kProjectFormatUnknown), _isBigEndian(false),
+ : Structural(runtime), _projectFormat(Data::kProjectFormatUnknown),
_haveGlobalObjectInfo(false), _haveProjectStructuralDef(false), _playMediaSignaller(new PlayMediaSignaller()),
_keyboardEventSignaller(new KeyboardEventSignaller()), _guessedVersion(MTropolisVersions::kMTropolisVersion1_0),
- _platform(kProjectPlatformUnknown) {
+ _platform(kProjectPlatformUnknown), _rootArchive(nullptr), _majorVersion(kProjectMajorVersionUnknown) {
}
Project::~Project() {
+ // Project teardown can be chaotic, we need to get rid of things in an orderly fashion.
+
+ // Remove all modifiers and structural children, which should unhook anything referencing an asset
+ _modifiers.clear();
+ _children.clear();
+
+ // Remove all global modifiers
+ _globalModifiers.clear();
+
+ // Unhook assets assets
+ _assets.clear();
+
+ // Unhook plug-ins
+ _plugIns.clear();
+
+ // Unhook cursor graphics
+ _cursorGraphics.reset();
+
+ // Close all segment streams
for (size_t i = 0; i < _segments.size(); i++)
closeSegmentStream(i);
+
+ // Last of all, release project resources
+ _resources.reset();
}
VThreadState Project::consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
@@ -6977,6 +7017,9 @@ void Project::loadFromDescription(const ProjectDescription &desc, const Hacks &h
_cursorGraphics = desc.getCursorGraphics();
_subtitles = desc.getSubtitles();
_platform = desc.getPlatform();
+ _rootArchive = desc.getRootArchive();
+ _projectRootDir = desc.getProjectRootDir();
+ _majorVersion = desc.getMajorVersion();
debug(1, "Loading new project...");
@@ -7008,49 +7051,44 @@ void Project::loadFromDescription(const ProjectDescription &desc, const Hacks &h
if (startValue == 1) {
// Windows format
- _isBigEndian = false;
_projectFormat = Data::kProjectFormatWindows;
} else if (startValue == 0) {
// Mac format
- _isBigEndian = true;
_projectFormat = Data::kProjectFormatMacintosh;
+ } else if (startValue == 8) {
+ // Cross-platform format
+ _projectFormat = Data::kProjectFormatNeutral;
} else {
warning("Unrecognized project segment header (startValue: %d)", startValue);
_projectFormat = Data::kProjectFormatWindows;
}
- Common::SeekableSubReadStreamEndian stream(baseStream, 2, baseStream->size(), _isBigEndian);
- const uint32 magic = stream.readUint32();
- const uint32 hdr1 = stream.readUint32();
- const uint32 hdr2 = stream.readUint32();
- if (magic != 0xaa55a5a5 || (hdr1 != 0 && hdr1 != 0x2000000) || hdr2 != 14) {
- error("Unrecognized project segment header (%x, %x, %d)", magic, hdr1, hdr2);
- }
+ Common::SeekableSubReadStream stream(baseStream, 2, baseStream->size());
- if (hdr1 == 0)
- _projectEngineVersion = Data::kProjectEngineVersion1;
- else
- _projectEngineVersion = Data::kProjectEngineVersion2;
+ Data::DataReader catReader(2, stream, (_projectFormat == Data::kProjectFormatMacintosh) ? Data::kDataFormatMacintosh : Data::kDataFormatWindows);
- Data::DataReader reader(2, stream, _projectFormat, _projectEngineVersion);
+ uint32 magic = 0;
+ uint32 hdr1 = 0;
+ uint32 hdr2 = 0;
+ if (!catReader.readMultiple(magic, hdr1, hdr2) || magic != 0xaa55a5a5 || (hdr1 != 0 && hdr1 != 0x2000000) || hdr2 != 14) {
+ error("Unrecognized project segment header (%x, %x, %d)", magic, hdr1, hdr2);
+ }
Common::SharedPtr<Data::DataObject> dataObject;
- Data::loadDataObject(_plugInRegistry.getDataLoaderRegistry(), reader, dataObject);
+ Data::loadDataObject(_plugInRegistry.getDataLoaderRegistry(), catReader, dataObject);
if (!dataObject || dataObject->getType() != Data::DataObjectTypes::kProjectHeader) {
error("Expected project header but found something else");
}
- Data::loadDataObject(plugInDataLoaderRegistry, reader, dataObject);
+ Data::loadDataObject(plugInDataLoaderRegistry, catReader, dataObject);
if (!dataObject || dataObject->getType() != Data::DataObjectTypes::kProjectCatalog) {
error("Expected project catalog but found something else");
}
Data::ProjectCatalog *catalog = static_cast<Data::ProjectCatalog *>(dataObject.get());
- if (catalog->segments.size() != desc.getSegments().size()) {
- error("Project declared a different number of segments than the project description provided");
- }
+ _segments.resize(catalog->segments.size());
debug(1, "Catalog loaded OK, identified %i streams", static_cast<int>(catalog->streams.size()));
@@ -7069,8 +7107,8 @@ void Project::loadFromDescription(const ProjectDescription &desc, const Hacks &h
streamDesc.streamType = kStreamTypeUnknown;
streamDesc.segmentIndex = srcStream.segmentIndexPlusOne - 1;
- streamDesc.size = srcStream.size;
- streamDesc.pos = srcStream.pos;
+ streamDesc.size = (_platform == kProjectPlatformMacintosh) ? srcStream.macSize : srcStream.winSize;
+ streamDesc.pos = (_platform == kProjectPlatformMacintosh) ? srcStream.macPos : srcStream.winPos;
}
// Locate the boot stream
@@ -7106,8 +7144,8 @@ void Project::loadSceneFromStream(const Common::SharedPtr<Structural> &scene, ui
openSegmentStream(segmentIndex);
- Common::SeekableSubReadStreamEndian stream(_segments[segmentIndex].weakStream, streamDesc.pos, streamDesc.pos + streamDesc.size, _isBigEndian);
- Data::DataReader reader(streamDesc.pos, stream, _projectFormat, _projectEngineVersion);
+ Common::SeekableSubReadStream stream(_segments[segmentIndex].weakStream, streamDesc.pos, streamDesc.pos + streamDesc.size);
+ Data::DataReader reader(streamDesc.pos, stream, (_platform == kProjectPlatformMacintosh) ? Data::kDataFormatMacintosh : Data::kDataFormatWindows);
if (getRuntime()->getHacks().mtiHispaniolaDamagedStringHack && scene->getName() == "C01b : Main Deck Helm Kidnap")
reader.setPermitDamagedStrings(true);
@@ -7234,8 +7272,8 @@ void Project::forceLoadAsset(uint32 assetID, Common::Array<Common::SharedPtr<Ass
openSegmentStream(segmentIndex);
- Common::SeekableSubReadStreamEndian stream(_segments[segmentIndex].weakStream, streamDesc.pos, streamDesc.pos + streamDesc.size, _isBigEndian);
- Data::DataReader reader(streamDesc.pos, stream, _projectFormat, _projectEngineVersion);
+ Common::SeekableSubReadStream stream(_segments[segmentIndex].weakStream, streamDesc.pos, streamDesc.pos + streamDesc.size);
+ Data::DataReader reader(streamDesc.pos, stream, (_projectFormat == Data::kProjectFormatMacintosh) ? Data::kDataFormatMacintosh : Data::kDataFormatWindows);
const Data::PlugInModifierRegistry &plugInDataLoaderRegistry = _plugInRegistry.getDataLoaderRegistry();
@@ -7295,13 +7333,43 @@ void Project::openSegmentStream(int segmentIndex) {
segment.rcStream.reset();
segment.weakStream = segment.desc.stream;
} else {
- Common::File *f = new Common::File();
- segment.rcStream.reset(f);
- segment.weakStream = f;
+ Common::Path defaultPath = _projectRootDir.appendComponent(segment.desc.filePath);
+
+ if (_platform == kProjectPlatformMacintosh)
+ segment.rcStream.reset(Common::MacResManager::openFileOrDataFork(defaultPath, *_rootArchive));
+ else
+ segment.rcStream.reset(_rootArchive->createReadStreamForMember(defaultPath));
+
+ if (!segment.rcStream) {
+ warning("Segment '%s' isn't in the project directory", segment.desc.filePath.c_str());
+
+ Common::ArchiveMemberList memberList;
+ Common::ArchiveMemberPtr locatedMember;
+
+ _rootArchive->listMembers(memberList);
+
+ for (const Common::ArchiveMemberPtr &member : memberList) {
+ if (member->getFileName().equalsIgnoreCase(segment.desc.filePath)) {
+ if (locatedMember)
+ error("Segment '%s' exists multiple times in the workspace, and isn't in the project directory, couldn't disambiguate", segment.desc.filePath.c_str());
+
+ locatedMember = member;
+ }
+ }
+
+ if (!locatedMember)
+ error("Segment '%s' is missing from the workspace", segment.desc.filePath.c_str());
- if (!f->open(segment.desc.filePath)) {
- error("Failed to open segment file %s", segment.desc.filePath.c_str());
+ if (_platform == kProjectPlatformMacintosh)
+ segment.rcStream.reset(Common::MacResManager::openFileOrDataFork(locatedMember->getPathInArchive(), *_rootArchive));
+ else
+ segment.rcStream.reset(locatedMember->createReadStream());
+
+ if (!segment.rcStream)
+ error("Failed to open segment file %s", segment.desc.filePath.c_str());
}
+
+ segment.weakStream = segment.rcStream.get();
}
segment.unloadSignaller.reset(new SegmentUnloadSignaller(this, segmentIndex));
@@ -7394,8 +7462,8 @@ void Project::loadBootStream(size_t streamIndex, const Hacks &hacks) {
size_t segmentIndex = streamDesc.segmentIndex;
openSegmentStream(segmentIndex);
- Common::SeekableSubReadStreamEndian stream(_segments[segmentIndex].weakStream, streamDesc.pos, streamDesc.pos + streamDesc.size, _isBigEndian);
- Data::DataReader reader(streamDesc.pos, stream, _projectFormat, _projectEngineVersion);
+ Common::SeekableSubReadStream stream(_segments[segmentIndex].weakStream, streamDesc.pos, streamDesc.pos + streamDesc.size);
+ Data::DataReader reader(streamDesc.pos, stream, (_platform == kProjectPlatformMacintosh) ? Data::kDataFormatMacintosh : Data::kDataFormatWindows);
ChildLoaderStack loaderStack;
AssetDefLoaderContext assetDefLoader;
@@ -7452,6 +7520,8 @@ void Project::loadBootStream(size_t streamIndex, const Hacks &hacks) {
loaderContext.type = ChildLoaderContext::kTypeProject;
loaderStack.contexts.push_back(loaderContext);
+
+ initAdditionalSegments(def->name);
} break;
case Data::DataObjectTypes::kStreamHeader:
case Data::DataObjectTypes::kUnknown19:
@@ -7693,6 +7763,30 @@ void Project::assignAssets(const Common::Array<Common::SharedPtr<Asset> >& asset
}
}
+void Project::initAdditionalSegments(const Common::String &projectName) {
+ for (uint segmentIndex = 1; segmentIndex < _segments.size(); segmentIndex++) {
+ Segment &segment = _segments[segmentIndex];
+
+ Common::String segmentName = projectName + Common::String::format("%i", static_cast<int>(segmentIndex + 1));
+
+ if (_projectFormat == Data::kProjectFormatNeutral) {
+ segmentName += ".mxx";
+ } else if (_projectFormat == Data::kProjectFormatWindows) {
+ if (_majorVersion == kProjectMajorVersion2)
+ segmentName += ".mxw";
+ else
+ segmentName += ".mpx";
+ } else if (_projectFormat == Data::kProjectFormatMacintosh) {
+ if (_majorVersion == kProjectMajorVersion2)
+ segmentName += ".mxm";
+ }
+
+ // Attempt to find the segment
+ segment.desc.filePath = segmentName;
+ segment.desc.volumeID = segmentIndex;
+ }
+}
+
void Project::loadContextualObject(size_t streamIndex, ChildLoaderStack &stack, const Data::DataObject &dataObject) {
ChildLoaderContext &topContext = stack.contexts.back();
const Data::DataObjectTypes::DataObjectType dataObjectType = dataObject.getType();
diff --git a/engines/mtropolis/runtime.h b/engines/mtropolis/runtime.h
index a27660add7a..f8b360c17de 100644
--- a/engines/mtropolis/runtime.h
+++ b/engines/mtropolis/runtime.h
@@ -1249,17 +1249,24 @@ private:
Common::HashMap<uint32, Common::SharedPtr<CursorGraphic> > _cursorGraphics;
};
+// The project platform is the platform that the project is running on (not the format of the project)
enum ProjectPlatform {
kProjectPlatformUnknown,
kProjectPlatformWindows,
kProjectPlatformMacintosh,
- KProjectPlatformCrossPlatform,
+};
+
+enum ProjectMajorVersion {
+ kProjectMajorVersionUnknown,
+
+ kProjectMajorVersion1,
+ kProjectMajorVersion2,
};
class ProjectDescription {
public:
- explicit ProjectDescription(ProjectPlatform platform);
+ ProjectDescription(ProjectPlatform platform, ProjectMajorVersion majorVersion, Common::Archive *rootArchive, const Common::Path &projectRootDir);
~ProjectDescription();
void addSegment(int volumeID, const char *filePath);
@@ -1279,9 +1286,15 @@ public:
const Common::Language &getLanguage() const;
ProjectPlatform getPlatform() const;
+ ProjectMajorVersion getMajorVersion() const;
+
+ Common::Archive *getRootArchive() const;
+ const Common::Path &getProjectRootDir() const;
const SubtitleTables &getSubtitles() const;
- void getSubtitles(const SubtitleTables &subs);
+ void setSubtitles(const SubtitleTables &subs);
+
+
private:
Common::Array<SegmentDescription> _segments;
@@ -1291,6 +1304,10 @@ private:
Common::Language _language;
SubtitleTables _subtitles;
ProjectPlatform _platform;
+ ProjectMajorVersion _majorVersion;
+
+ Common::Archive *_rootArchive;
+ Common::Path _projectRootDir;
};
struct VolumeState {
@@ -2003,6 +2020,8 @@ public:
const Common::Array<Common::SharedPtr<Modifier> > &getModifiers() const override;
void appendModifier(const Common::SharedPtr<Modifier> &modifier) override;
+ void clear();
+
private:
Common::Array<Common::SharedPtr<Modifier> > _modifiers;
};
@@ -2469,6 +2488,7 @@ private:
SegmentDescription desc;
Common::SharedPtr<Common::SeekableReadStream> rcStream;
+
Common::SeekableReadStream *weakStream;
Common::SharedPtr<SegmentUnloadSignaller> unloadSignaller;
};
@@ -2519,13 +2539,13 @@ private:
void assignAssets(const Common::Array<Common::SharedPtr<Asset> > &assets, const Hacks &hacks);
+ void initAdditionalSegments(const Common::String &projectName);
+
Common::Array<Segment> _segments;
Common::Array<StreamDesc> _streams;
Common::Array<LabelTree> _labelTree;
Common::Array<LabelSuperGroup> _labelSuperGroups;
Data::ProjectFormat _projectFormat;
- Data::ProjectEngineVersion _projectEngineVersion;
- bool _isBigEndian;
Common::Array<AssetDesc *> _assetsByID;
Common::Array<AssetDesc> _realAssets;
@@ -2554,6 +2574,10 @@ private:
MTropolisVersions::MTropolisVersion _guessedVersion;
ProjectPlatform _platform;
+
+ Common::Archive *_rootArchive;
+ Common::Path _projectRootDir;
+ ProjectMajorVersion _majorVersion;
};
class Section : public Structural {
diff --git a/engines/mtropolis/vfs.cpp b/engines/mtropolis/vfs.cpp
new file mode 100644
index 00000000000..e612af55bc8
--- /dev/null
+++ b/engines/mtropolis/vfs.cpp
@@ -0,0 +1,238 @@
+/* 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 "mtropolis/vfs.h"
+
+#include "common/punycode.h"
+
+namespace MTropolis {
+
+VirtualFileSystemLayout::VirtualFileSystemLayout() : _pathSeparator('/') {
+}
+
+VirtualFileSystem::VirtualFileSystem(const VirtualFileSystemLayout &layout) : _pathSeparator(layout._pathSeparator), _workspaceRoot(layout._workspaceRoot) {
+ Common::Array<TempLayoutFile> tempLayoutFiles;
+ Common::HashMap<Common::String, uint> pathToTLF;
+ Common::HashMap<Common::String, Common::String> canonicalPathToVirtualPath;
+ Common::HashMap<Common::String, uint> canonicalPathToTLF;
+
+ for (const VirtualFileSystemLayout::ArchiveJunction &arcJunction : layout._archiveJunctions) {
+ Common::String prefix = arcJunction._archiveName + _pathSeparator;
+ Common::ArchiveMemberList arcMemberList;
+
+ arcJunction._archive->listMembers(arcMemberList);
+
+ for (const Common::ArchiveMemberPtr &arcMember : arcMemberList) {
+ TempLayoutFile tlf;
+ tlf._archiveMember = arcMember;
+ tlf._expandedPath = (prefix + arcMember->getPathInArchive().toString(_pathSeparator));
+ tlf._expandedPathCanonical = tlf._expandedPath;
+ tlf._expandedPathCanonical.toLowercase();
+
+ Common::HashMap<Common::String, uint>::const_iterator indexIt = pathToTLF.find(tlf._expandedPath);
+
+ if (indexIt != pathToTLF.end())
+ tempLayoutFiles[indexIt->_value] = tlf;
+ else {
+ pathToTLF[tlf._expandedPath] = tempLayoutFiles.size();
+ tempLayoutFiles.push_back(tlf);
+ }
+ }
+ }
+
+ for (const VirtualFileSystemLayout::PathJunction &pjunc : layout._pathJunctions) {
+ Common::String destPathFile = pjunc._destPath;
+ destPathFile.toLowercase();
+
+ Common::String destPathDir = destPathFile + _pathSeparator;
+
+ for (uint i = 0; i < tempLayoutFiles.size(); i++) {
+ const TempLayoutFile &tlf = tempLayoutFiles[i];
+
+ if (tlf._expandedPathCanonical == destPathFile || tlf._expandedPathCanonical.hasPrefix(destPathDir)) {
+ Common::String translatedPath = pjunc._srcPath + tlf._expandedPath.substr(destPathFile.size());
+
+ Common::String canonicalPath = canonicalizePath(Common::Path(translatedPath, _pathSeparator));
+
+ canonicalPathToTLF[canonicalPath] = i;
+ canonicalPathToVirtualPath[canonicalPath] = translatedPath;
+ }
+ }
+ }
+
+ for (const Common::String &excl : layout._exclusions) {
+ Common::String canonicalPath = canonicalizePath(Common::Path(excl, _pathSeparator));
+
+ canonicalPathToTLF.erase(canonicalPath);
+ }
+
+ _virtualFiles.reserve(canonicalPathToTLF.size());
+
+ for (const Common::HashMap<Common::String, uint>::Node &ctofNode : canonicalPathToTLF) {
+ Common::HashMap<Common::String, Common::String>::const_iterator ctovIt = canonicalPathToVirtualPath.find(ctofNode._key);
+
+ assert(ctovIt != canonicalPathToVirtualPath.end());
+
+ VirtualFile vf;
+ vf._archiveMember = tempLayoutFiles[ctofNode._value]._archiveMember;
+ vf._virtualPath = ctovIt->_value;
+
+ _virtualFiles.push_back(vf);
+ }
+
+ Common::sort(_virtualFiles.begin(), _virtualFiles.end(), sortVirtualFiles);
+
+ for (uint i = 0; i < _virtualFiles.size(); i++) {
+ VirtualFile &vf = _virtualFiles[i];
+
+ _pathToVirtualFileIndex[canonicalizePath(vf._virtualPath)] = i;
+ vf._vfsArchiveMember = Common::ArchiveMemberPtr(new VFSArchiveMember(&vf, _pathSeparator));
+ }
+}
+
+bool VirtualFileSystem::hasFile(const Common::Path &path) const {
+ return getVirtualFile(path) != nullptr;
+}
+
+bool VirtualFileSystem::isPathDirectory(const Common::Path &path) const {
+ const VirtualFile *vf = getVirtualFile(path);
+ if (vf)
+ return vf->_archiveMember->isDirectory();
+
+ return false;
+}
+
+int VirtualFileSystem::listMembers(Common::ArchiveMemberList &list) const {
+ int numMembers = 0;
+ for (const VirtualFile &vf : _virtualFiles) {
+ list.push_back(vf._vfsArchiveMember);
+ numMembers++;
+ }
+
+ return numMembers;
+}
+
+const Common::ArchiveMemberPtr VirtualFileSystem::getMember(const Common::Path &path) const {
+ const VirtualFile *vf = getVirtualFile(path);
+ if (vf)
+ return vf->_vfsArchiveMember;
+
+ return nullptr;
+}
+
+Common::SeekableReadStream *VirtualFileSystem::createReadStreamForMember(const Common::Path &path) const {
+ const VirtualFile *vf = getVirtualFile(path);
+ if (vf)
+ return vf->_archiveMember->createReadStream();
+
+ return nullptr;
+}
+
+Common::SeekableReadStream *VirtualFileSystem::createReadStreamForMemberAltStream(const Common::Path &path, Common::AltStreamType altStreamType) const {
+ const VirtualFile *vf = getVirtualFile(path);
+ if (vf)
+ return vf->_archiveMember->createReadStreamForAltStream(altStreamType);
+
+ return nullptr;
+}
+
+char VirtualFileSystem::getPathSeparator() const {
+ return _pathSeparator;
+}
+
+int VirtualFileSystem::listMatchingMembers(Common::ArchiveMemberList &list, const Common::Path &pattern, bool matchPathComponents) const {
+ Common::ArchiveMemberList allNames;
+ listMembers(allNames);
+
+ Common::String patternString = pattern.toString(getPathSeparator());
+ int matches = 0;
+
+ char pathSepString[2] = {getPathSeparator(), '\0'};
+
+ const char *wildcardExclusions = matchPathComponents ? NULL : pathSepString;
+
+ Common::ArchiveMemberList::const_iterator it = allNames.begin();
+ for (; it != allNames.end(); ++it) {
+ if ((*it)->getName().matchString(patternString, true, wildcardExclusions)) {
+ list.push_back(*it);
+ matches++;
+ }
+ }
+
+ return matches;
+}
+
+const VirtualFileSystem::VirtualFile *VirtualFileSystem::getVirtualFile(const Common::Path &path) const {
+ Common::HashMap<Common::String, uint>::const_iterator it = _pathToVirtualFileIndex.find(canonicalizePath(path));
+
+ if (it == _pathToVirtualFileIndex.end())
+ return nullptr;
+
+ return &_virtualFiles[it->_value];
+}
+
+bool VirtualFileSystem::sortVirtualFiles(const VirtualFile &a, const VirtualFile &b) {
+ return a._virtualPath < b._virtualPath;
+}
+
+Common::String VirtualFileSystem::canonicalizePath(const Common::Path &path) const {
+ Common::String result = path.toString(_pathSeparator);
+ result.toLowercase();
+
+ return result;
+}
+
+VirtualFileSystem::VFSArchiveMember::VFSArchiveMember(const VirtualFile *virtualFile, char pathSeparator) : _virtualFile(virtualFile), _pathSeparator(pathSeparator) {
+}
+
+Common::SeekableReadStream *VirtualFileSystem::VFSArchiveMember::createReadStream() const {
+ return _virtualFile->_archiveMember->createReadStream();
+}
+
+Common::SeekableReadStream *VirtualFileSystem::VFSArchiveMember::createReadStreamForAltStream(Common::AltStreamType altStreamType) const {
+ return _virtualFile->_archiveMember->createReadStreamForAltStream(altStreamType);
+}
+
+Common::String VirtualFileSystem::VFSArchiveMember::getName() const {
+ return _virtualFile->_virtualPath;
+}
+
+Common::Path VirtualFileSystem::VFSArchiveMember::getPathInArchive() const {
+ return Common::Path(_virtualFile->_virtualPath, _pathSeparator);
+}
+
+Common::String VirtualFileSystem::VFSArchiveMember::getFileName() const {
+ return _virtualFile->_archiveMember->getFileName();
+}
+
+bool VirtualFileSystem::VFSArchiveMember::isDirectory() const {
+ return _virtualFile->_archiveMember->isDirectory();
+}
+
+void VirtualFileSystem::VFSArchiveMember::listChildren(Common::ArchiveMemberList &childList, const char *pattern) const {
+ return _virtualFile->_archiveMember->listChildren(childList, pattern);
+}
+
+Common::U32String VirtualFileSystem::VFSArchiveMember::getDisplayName() const {
+ return _virtualFile->_archiveMember->getDisplayName();
+}
+
+} // End of namespace MTropolis
diff --git a/engines/mtropolis/vfs.h b/engines/mtropolis/vfs.h
new file mode 100644
index 00000000000..c64ab56ce3b
--- /dev/null
+++ b/engines/mtropolis/vfs.h
@@ -0,0 +1,115 @@
+/* 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_VFS_H
+#define MTROPOLIS_VFS_H
+
+#include "common/archive.h"
+#include "common/hash-str.h"
+
+namespace MTropolis {
+
+struct VirtualFileSystemLayout {
+ VirtualFileSystemLayout();
+
+ struct PathJunction {
+ Common::String _srcPath;
+ Common::String _destPath;
+ };
+
+ struct ArchiveJunction {
+ Common::String _archiveName;
+ Common::Archive *_archive;
+ };
+
+ char _pathSeparator;
+ Common::Path _workspaceRoot;
+
+ Common::Array<PathJunction> _pathJunctions;
+ Common::Array<ArchiveJunction> _archiveJunctions;
+ Common::Array<Common::String> _exclusions;
+};
+
+class VirtualFileSystem : public Common::Archive {
+public:
+ explicit VirtualFileSystem(const VirtualFileSystemLayout &layout);
+
+ bool hasFile(const Common::Path &path) const override;
+ bool isPathDirectory(const Common::Path &path) const override;
+ int listMembers(Common::ArchiveMemberList &list) const override;
+ const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
+ Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
+
+ Common::SeekableReadStream *createReadStreamForMemberAltStream(const Common::Path &path, Common::AltStreamType altStreamType) const override;
+
+ int listMatchingMembers(Common::ArchiveMemberList &list, const Common::Path &pattern, bool matchPathComponents = false) const override;
+
+ char getPathSeparator() const override;
+
+private:
+ struct VirtualFile {
+ Common::String _virtualPath;
+ Common::ArchiveMemberPtr _archiveMember;
+
+ Common::ArchiveMemberPtr _vfsArchiveMember;
+ };
+
+ struct TempLayoutFile {
+ Common::String _expandedPath;
+ Common::String _expandedPathCanonical;
+ Common::ArchiveMemberPtr _archiveMember;
+ };
+
+ class VFSArchiveMember : public Common::ArchiveMember {
+ public:
+ VFSArchiveMember(const VirtualFile *virtualFile, char pathSeparator);
+
+ Common::SeekableReadStream *createReadStream() const override;
+ Common::SeekableReadStream *createReadStreamForAltStream(Common::AltStreamType altStreamType) const override;
+
+ Common::String getName() const override;
+
+ Common::Path getPathInArchive() const override;
+ Common::String getFileName() const override;
+ bool isDirectory() const override;
+ void listChildren(Common::ArchiveMemberList &childList, const char *pattern) const override;
+ Common::U32String getDisplayName() const override;
+
+ private:
+ const VirtualFile *_virtualFile;
+ char _pathSeparator;
+ };
+
+ const VirtualFile *getVirtualFile(const Common::Path &path) const;
+ static bool sortVirtualFiles(const VirtualFile &a, const VirtualFile &b);
+
+ Common::String canonicalizePath(const Common::Path &path) const;
+
+ char _pathSeparator;
+ Common::Path _workspaceRoot;
+
+ Common::Array<VirtualFile> _virtualFiles;
+ Common::HashMap<Common::String, uint> _pathToVirtualFileIndex;
+};
+
+} // End of namespace MTropolis
+
+#endif
Commit: cab8f4cae950c0febe1133e1175842ae3ee48563
https://github.com/scummvm/scummvm/commit/cab8f4cae950c0febe1133e1175842ae3ee48563
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2023-12-13T22:06:27-05:00
Commit Message:
MTROPOLIS: Fix up Obsidian Japanese
Changed paths:
engines/mtropolis/boot.cpp
diff --git a/engines/mtropolis/boot.cpp b/engines/mtropolis/boot.cpp
index fc5b15977ac..57d14bd9e39 100644
--- a/engines/mtropolis/boot.cpp
+++ b/engines/mtropolis/boot.cpp
@@ -1612,7 +1612,7 @@ void BootScriptContext::addSubtitles(const Common::String &linesFile, const Comm
}
void BootScriptContext::addExclusion(const Common::String &virtualPath) {
- _vfsLayout._exclusions.push_back(virtualPath);
+ _vfsLayout._exclusions.push_back(Common::String(_isMac ? "workspace:" : "workspace/") + virtualPath);
}
void BootScriptContext::setResolution(uint width, uint height) {
@@ -1641,7 +1641,7 @@ void BootScriptContext::bootObsidianRetailMacEn() {
addJunction("Obsidian Data 5", "fs:Obsidian Data 5");
addJunction("Obsidian Data 6", "fs:Obsidian Data 6");
- addExclusion("workspace:Obsidian Data 0");
+ addExclusion("Obsidian Data 0");
addSubtitles("subtitles_lines_obsidian_en.csv", "subtitles_speakers_obsidian_en.csv", "subtitles_asset_mapping_obsidian_en.csv", "subtitles_modifier_mapping_obsidian_en.csv");
}
@@ -1652,14 +1652,14 @@ void BootScriptContext::bootObsidianRetailMacJp() {
addArchive(kArchiveTypeStuffIt, "installer", "fs:xn--u9j9ecg0a2fsa1io6k6jkdc2k");
- addJunction("workspace", "installer");
- addJunction("workspace:Obsidian Data 2", "fs:Obsidian Data 2");
- addJunction("workspace:Obsidian Data 3", "fs:Obsidian Data 3");
- addJunction("workspace:Obsidian Data 4", "fs:Obsidian Data 4");
- addJunction("workspace:Obsidian Data 5", "fs:Obsidian Data 5");
- addJunction("workspace:Obsidian Data 6", "fs:Obsidian Data 6");
+ addJunction("", "installer");
+ addJunction("Obsidian Data 2", "fs:Obsidian Data 2");
+ addJunction("Obsidian Data 3", "fs:Obsidian Data 3");
+ addJunction("Obsidian Data 4", "fs:Obsidian Data 4");
+ addJunction("Obsidian Data 5", "fs:Obsidian Data 5");
+ addJunction("Obsidian Data 6", "fs:Obsidian Data 6");
- addExclusion("workspace:Obsidian Data 0");
+ addExclusion("Obsidian Data 0");
}
void BootScriptContext::bootObsidianGeneric() {
@@ -1686,14 +1686,14 @@ void BootScriptContext::bootMTIRetailMac() {
addPlugIn(kPlugInMTI);
addPlugIn(kPlugInStandard);
- addJunction("workspace:mPlayer PPC", "fs:MPlayer PPC");
- addJunction("workspace:mPlayer PPC:Resource", "fs:MPlayer PPC:Resource");
- addJunction("workspace:MTI1", "fs:xn--MTI1-8b7a");
- addJunction("workspace:MTI2", "fs:MTI2");
- addJunction("workspace:MTI3", "fs:MTI3");
- addJunction("workspace:MTI4", "fs:MTI4");
+ addJunction("mPlayer PPC", "fs:MPlayer PPC");
+ addJunction("mPlayer PPC:Resource", "fs:MPlayer PPC:Resource");
+ addJunction("MTI1", "fs:xn--MTI1-8b7a");
+ addJunction("MTI2", "fs:MTI2");
+ addJunction("MTI3", "fs:MTI3");
+ addJunction("MTI4", "fs:MTI4");
- addJunction("workspace:VIDEO", "fs:VIDEO");
+ addJunction("VIDEO", "fs:VIDEO");
}
void BootScriptContext::bootMTIGeneric() {
Commit: cf97b636f3c24b4253fe75de27353f5f07ec6c4d
https://github.com/scummvm/scummvm/commit/cf97b636f3c24b4253fe75de27353f5f07ec6c4d
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2023-12-13T22:06:27-05:00
Commit Message:
MTROPOLIS: Fix SPQR boot
Changed paths:
engines/mtropolis/boot.cpp
diff --git a/engines/mtropolis/boot.cpp b/engines/mtropolis/boot.cpp
index 57d14bd9e39..f97c5d81f82 100644
--- a/engines/mtropolis/boot.cpp
+++ b/engines/mtropolis/boot.cpp
@@ -1448,6 +1448,7 @@ class BootScriptContext {
public:
enum PlugIn {
kPlugInMTI,
+ kPlugInSPQR,
kPlugInStandard,
kPlugInObsidian,
};
@@ -1468,6 +1469,8 @@ public:
void bootObsidianRetailWinDe();
void bootMTIRetailMac();
void bootMTIGeneric();
+ void bootSPQRMac();
+ void bootSPQRWin();
void bootGeneric();
void bootUsingBootScript();
@@ -1701,6 +1704,21 @@ void BootScriptContext::bootMTIGeneric() {
addPlugIn(kPlugInStandard);
}
+void BootScriptContext::bootSPQRMac() {
+ addPlugIn(kPlugInSPQR);
+ addPlugIn(kPlugInStandard);
+
+ addArchive(kArchiveTypeMacVISE, "installer", "fs:Install.vct");
+
+ addJunction("", "fs:GAME");
+ addJunction("", "installer");
+}
+
+void BootScriptContext::bootSPQRWin() {
+ addPlugIn(kPlugInSPQR);
+ addPlugIn(kPlugInStandard);
+}
+
void BootScriptContext::bootGeneric() {
addPlugIn(kPlugInStandard);
}
@@ -1758,7 +1776,8 @@ void BootScriptContext::bootUsingBootScript() {
void BootScriptContext::executeFunction(const Common::String &functionName, const Common::Array<Common::String> ¶mTokens) {
const EnumBinding plugInEnum[] = {ENUM_BINDING(kPlugInMTI),
ENUM_BINDING(kPlugInStandard),
- ENUM_BINDING(kPlugInObsidian)};
+ ENUM_BINDING(kPlugInObsidian),
+ ENUM_BINDING(kPlugInSPQR)};
const EnumBinding bitDepthEnum[] = {ENUM_BINDING(kBitDepthAuto),
ENUM_BINDING(kBitDepth8),
@@ -2021,12 +2040,12 @@ const Game games[] = {
// SPQR: The Empire's Darkest Hour - Retail - Windows - English
{
MTBOOT_SPQR_RETAIL_WIN,
- &BootScriptContext::bootGeneric
+ &BootScriptContext::bootSPQRWin
},
// SPQR: The Empire's Darkest Hour - Retail - Macintosh - English
{
MTBOOT_SPQR_RETAIL_MAC,
- &BootScriptContext::bootGeneric
+ &BootScriptContext::bootSPQRMac
},
// Star Trek: The Game Show - Demo - Windows
{
@@ -2061,6 +2080,11 @@ Common::SharedPtr<MTropolis::PlugIn> loadMTIPlugIn(const MTropolisGameDescriptio
return mtiPlugIn;
}
+Common::SharedPtr<MTropolis::PlugIn> loadSPQRPlugIn(const MTropolisGameDescription &gameDesc) {
+ Common::SharedPtr<MTropolis::PlugIn> spqrPlugIn(PlugIns::createSPQR());
+ return spqrPlugIn;
+}
+
enum PlayerType {
kPlayerTypeNone,
@@ -2172,7 +2196,7 @@ void findWindowsPlayer(Common::Archive &fs, Common::Path &resolvedPath, PlayerTy
numPlayersInCategory++;
}
- if (numPlayersInCategory == 0)
+ if (numPlayersInCategory == 0 || bestPlayerType == kPlayerTypeNone)
error("Couldn't find any mTropolis Player executables");
if (numPlayersInCategory != 1)
@@ -2251,7 +2275,7 @@ void findMacPlayer(Common::Archive &fs, Common::Path &resolvedPath, PlayerType &
numPlayersInCategory++;
}
- if (numPlayersInCategory == 0)
+ if (numPlayersInCategory == 0 || bestPlayerType == kPlayerTypeNone)
error("Couldn't find any mTropolis Player applications");
if (numPlayersInCategory != 1)
@@ -2774,6 +2798,9 @@ BootConfiguration bootProject(const MTropolisGameDescription &gameDesc) {
case Boot::BootScriptContext::kPlugInMTI:
plugIns.push_back(Boot::loadMTIPlugIn(gameDesc));
break;
+ case Boot::BootScriptContext::kPlugInSPQR:
+ plugIns.push_back(Boot::loadSPQRPlugIn(gameDesc));
+ break;
default:
error("Unknown plug-in ID");
}
More information about the Scummvm-git-logs
mailing list