From 7b4c6d6a35f00e46d9021b236393a40aa54e6a6e Mon Sep 17 00:00:00 2001 From: Cameron Cawley Date: Sat, 30 Mar 2019 23:30:10 +0000 Subject: GLK: Improved detection of Blorb files --- engines/glk/blorb.cpp | 141 ++++++++++++++++++++----------------- engines/glk/blorb.h | 45 +++++++++++- engines/glk/frotz/detection.cpp | 21 +++--- engines/glk/glulxe/detection.cpp | 15 ++-- engines/glk/magnetic/detection.cpp | 3 - engines/glk/scott/detection.cpp | 45 ++++++------ engines/glk/tads/detection.cpp | 20 ++++-- 7 files changed, 178 insertions(+), 112 deletions(-) diff --git a/engines/glk/blorb.cpp b/engines/glk/blorb.cpp index ce86dee5f7..ebf5e0150b 100644 --- a/engines/glk/blorb.cpp +++ b/engines/glk/blorb.cpp @@ -24,32 +24,6 @@ namespace Glk { -enum { - ID_FORM = MKTAG('F', 'O', 'R', 'M'), - ID_IFRS = MKTAG('I', 'F', 'R', 'S'), - ID_RIdx = MKTAG('R', 'I', 'd', 'x'), - - ID_Snd = MKTAG('S', 'n', 'd', ' '), - ID_Exec = MKTAG('E', 'x', 'e', 'c'), - ID_Pict = MKTAG('P', 'i', 'c', 't'), - ID_Data = MKTAG('D', 'a', 't', 'a'), - - ID_Copyright = MKTAG('(', 'c', ')', ' '), - ID_AUTH = MKTAG('A', 'U', 'T', 'H'), - ID_ANNO = MKTAG('A', 'N', 'N', 'O'), - - ID_JPEG = MKTAG('J', 'P', 'E', 'G'), - ID_PNG = MKTAG('P', 'N', 'G', ' '), - ID_Rect = MKTAG('R', 'e', 'c', 't'), - - ID_MIDI = MKTAG('M', 'I', 'D', 'I'), - ID_MP3 = MKTAG('M', 'P', '3', ' '), - ID_WAVE = MKTAG('W', 'A', 'V', 'E'), - ID_AIFF = MKTAG('A', 'I', 'F', 'F'), - ID_OGG = MKTAG('O', 'G', 'G', ' '), - ID_MOD = MKTAG('M', 'O', 'D', ' ') -}; - /*--------------------------------------------------------------------------*/ Blorb::Blorb(const Common::String &filename, InterpreterType interpType) : @@ -111,39 +85,18 @@ Common::ErrorCode Blorb::load() { // First, chew through the file and index the chunks Common::File f; if ((!_filename.empty() && !f.open(_filename)) || - (_filename.empty() && !f.open(_fileNode)) || - f.size() < 12) + (_filename.empty() && !f.open(_fileNode))) return Common::kReadingFailed; - if (f.readUint32BE() != ID_FORM) - return Common::kReadingFailed; - f.readUint32BE(); - if (f.readUint32BE() != ID_IFRS) - return Common::kReadingFailed; - if (f.readUint32BE() != ID_RIdx) + if (!isBlorb(f)) return Common::kReadingFailed; - f.readUint32BE(); - uint count = f.readUint32BE(); - - // First read in the resource index - for (uint idx = 0; idx < count; ++idx) { - ChunkEntry ce; - ce._type = f.readUint32BE(); - ce._number = f.readUint32BE(); - ce._offset = f.readUint32BE(); - - _chunks.push_back(ce); - } + if (!readRIdx(f, _chunks)) + return Common::kReadingFailed; // Further iterate through the resources for (uint idx = 0; idx < _chunks.size(); ++idx) { ChunkEntry &ce = _chunks[idx]; - f.seek(ce._offset); - ce._offset += 8; - - ce._id = f.readUint32BE(); - ce._size = f.readUint32BE(); if (ce._type == ID_Pict) { ce._filename = Common::String::format("pic%u", ce._number); @@ -173,22 +126,21 @@ Common::ErrorCode Blorb::load() { ce._filename = Common::String::format("data%u", ce._number); } else if (ce._type == ID_Exec) { - char buffer[5]; - WRITE_BE_UINT32(buffer, ce._id); - buffer[4] = '\0'; - Common::String type(buffer); - if ( - (_interpType == INTERPRETER_FROTZ && type == "ZCOD") || - (_interpType == INTERPRETER_GLULXE && type == "GLUL") || - (_interpType == INTERPRETER_TADS2 && type == "TAD2") || - (_interpType == INTERPRETER_TADS3 && type == "TAD3") || - (_interpType == INTERPRETER_HUGO && type == "HUGO") || - (_interpType == INTERPRETER_SCOTT && type == "SAAI") + (_interpType == INTERPRETER_FROTZ && ce._id == ID_ZCOD) || + (_interpType == INTERPRETER_GLULXE && ce._id == ID_GLUL) || + (_interpType == INTERPRETER_TADS2 && ce._id == ID_TAD2) || + (_interpType == INTERPRETER_TADS3 && ce._id == ID_TAD3) || + (_interpType == INTERPRETER_HUGO && ce._id == ID_HUGO) || + (_interpType == INTERPRETER_SCOTT && ce._id == ID_SAAI) ) { // Game executable ce._filename = "game"; } else { + char buffer[5]; + WRITE_BE_UINT32(buffer, ce._id); + buffer[4] = '\0'; + Common::String type(buffer); ce._filename = type; } } @@ -197,9 +149,68 @@ Common::ErrorCode Blorb::load() { return Common::kNoError; } -bool Blorb::isBlorb(const Common::String &filename) { - return filename.hasSuffixIgnoreCase(".blorb") || filename.hasSuffixIgnoreCase(".zblorb") - || filename.hasSuffixIgnoreCase(".gblorb") || filename.hasSuffixIgnoreCase(".blb"); +bool Blorb::readRIdx(Common::SeekableReadStream &stream, Common::Array &chunks) { + if (stream.readUint32BE() != ID_RIdx) + return false; + + stream.readUint32BE(); + uint count = stream.readUint32BE(); + + // First read in the resource index + for (uint idx = 0; idx < count; ++idx) { + ChunkEntry ce; + ce._type = stream.readUint32BE(); + ce._number = stream.readUint32BE(); + ce._offset = stream.readUint32BE(); + + chunks.push_back(ce); + } + + // Further iterate through the resources + for (uint idx = 0; idx < chunks.size(); ++idx) { + ChunkEntry &ce = chunks[idx]; + stream.seek(ce._offset); + ce._offset += 8; + + ce._id = stream.readUint32BE(); + ce._size = stream.readUint32BE(); + } + + return true; +} + +bool Blorb::isBlorb(Common::SeekableReadStream &stream, uint32 type) { + if (stream.size() < 12) + return false; + if (stream.readUint32BE() != ID_FORM) + return false; + stream.readUint32BE(); + if (stream.readUint32BE() != ID_IFRS) + return false; + + if (type == 0) + return true; + + Common::Array chunks; + if (!readRIdx(stream, chunks)) + return false; + + // Further iterate through the resources + for (uint idx = 0; idx < chunks.size(); ++idx) { + ChunkEntry &ce = chunks[idx]; + if (ce._type == ID_Exec && ce._id == type) + return true; + } + + return false; +} + +bool Blorb::isBlorb(const Common::String &filename, uint32 type) { + Common::File f; + if (!filename.empty() && !f.open(filename)) + return false; + + return isBlorb(f, type); } } // End of namespace Glk diff --git a/engines/glk/blorb.h b/engines/glk/blorb.h index 18a84a5f08..e5dde288ac 100644 --- a/engines/glk/blorb.h +++ b/engines/glk/blorb.h @@ -42,6 +42,39 @@ struct ChunkEntry { Common::String _filename; }; +enum { + ID_FORM = MKTAG('F', 'O', 'R', 'M'), + ID_IFRS = MKTAG('I', 'F', 'R', 'S'), + ID_RIdx = MKTAG('R', 'I', 'd', 'x'), + + ID_Snd = MKTAG('S', 'n', 'd', ' '), + ID_Exec = MKTAG('E', 'x', 'e', 'c'), + ID_Pict = MKTAG('P', 'i', 'c', 't'), + ID_Data = MKTAG('D', 'a', 't', 'a'), + + ID_Copyright = MKTAG('(', 'c', ')', ' '), + ID_AUTH = MKTAG('A', 'U', 'T', 'H'), + ID_ANNO = MKTAG('A', 'N', 'N', 'O'), + + ID_ZCOD = MKTAG('Z', 'C', 'O', 'D'), + ID_GLUL = MKTAG('G', 'L', 'U', 'L'), + ID_TAD2 = MKTAG('T', 'A', 'D', '2'), + ID_TAD3 = MKTAG('T', 'A', 'D', '3'), + ID_HUGO = MKTAG('H', 'U', 'G', 'O'), + ID_SAAI = MKTAG('S', 'A', 'A', 'I'), + + ID_JPEG = MKTAG('J', 'P', 'E', 'G'), + ID_PNG = MKTAG('P', 'N', 'G', ' '), + ID_Rect = MKTAG('R', 'e', 'c', 't'), + + ID_MIDI = MKTAG('M', 'I', 'D', 'I'), + ID_MP3 = MKTAG('M', 'P', '3', ' '), + ID_WAVE = MKTAG('W', 'A', 'V', 'E'), + ID_AIFF = MKTAG('A', 'I', 'F', 'F'), + ID_OGG = MKTAG('O', 'G', 'G', ' '), + ID_MOD = MKTAG('M', 'O', 'D', ' ') +}; + /** * Blorb file manager */ @@ -94,10 +127,20 @@ public: */ virtual Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const override; + /** + * Read the RIdx section from the stream. + */ + static bool readRIdx(Common::SeekableReadStream &stream, Common::Array &chunks); + + /** + * Returns true if a given file is a Blorb file + */ + static bool isBlorb(Common::SeekableReadStream &stream, uint32 type = 0); + /** * Returns true if a given filename specifies a Blorb file */ - static bool isBlorb(const Common::String &filename); + static bool isBlorb(const Common::String &filename, uint32 type = 0); }; } // End of namespace Glk diff --git a/engines/glk/frotz/detection.cpp b/engines/glk/frotz/detection.cpp index 273ca37342..4df4fd2be8 100644 --- a/engines/glk/frotz/detection.cpp +++ b/engines/glk/frotz/detection.cpp @@ -57,7 +57,7 @@ GameDescriptor FrotzMetaEngine::findGame(const char *gameId) { bool FrotzMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) { const char *const EXTENSIONS[] = { ".z1", ".z2", ".z3", ".z4", ".z5", ".z6", ".z7", ".z8", - ".zblorb", ".dat", ".zip", nullptr }; + ".dat", ".zip", nullptr }; // Loop through the files of the folder for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { @@ -65,11 +65,9 @@ bool FrotzMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &g if (file->isDirectory()) continue; Common::String filename = file->getName(); - bool hasExt = false; + bool hasExt = false, isBlorb = false; for (const char *const *ext = &EXTENSIONS[0]; *ext && !hasExt; ++ext) hasExt = filename.hasSuffixIgnoreCase(*ext); - if (!hasExt) - continue; // Open up the file and calculate the md5, and get the serial Common::File gameFile; @@ -79,15 +77,19 @@ bool FrotzMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &g size_t filesize = gameFile.size(); char serial[9] = ""; bool emptyBlorb = false; + gameFile.seek(0); + isBlorb = Blorb::isBlorb(gameFile, ID_ZCOD); - if (!filename.hasSuffixIgnoreCase(".zblorb")) { + if (!isBlorb) { + if (!hasExt) { + gameFile.close(); + continue; + } gameFile.seek(18); strcpy(&serial[0], "\""); gameFile.read(&serial[1], 6); strcpy(&serial[7], "\""); - } - gameFile.close(); - if (filename.hasSuffixIgnoreCase(".zblorb")) { + } else { Blorb b(*file, INTERPRETER_FROTZ); Common::SeekableReadStream *f = b.createReadStreamForMember("game"); emptyBlorb = f == nullptr; @@ -100,13 +102,14 @@ bool FrotzMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &g delete f; } } + gameFile.close(); // Check for known games. Note that there has been some variation in exact filesizes // for Infocom games due to padding at the end of files. So we match on md5s for the // first 5Kb, and only worry about filesize for more recent Blorb based Zcode games const FrotzGameDescription *p = FROTZ_GAMES; while (p->_gameId && p->_md5 && (md5 != p->_md5 || - (filesize != p->_filesize && filename.hasSuffix(".zblorb")))) + (filesize != p->_filesize && isBlorb))) ++p; DetectedGame gd; diff --git a/engines/glk/glulxe/detection.cpp b/engines/glk/glulxe/detection.cpp index 06ce40362d..64c6eabb5a 100644 --- a/engines/glk/glulxe/detection.cpp +++ b/engines/glk/glulxe/detection.cpp @@ -22,6 +22,7 @@ #include "glk/glulxe/detection.h" #include "glk/glulxe/detection_tables.h" +#include "glk/blorb.h" #include "common/debug.h" #include "common/file.h" #include "common/md5.h" @@ -46,7 +47,7 @@ GameDescriptor GlulxeMetaEngine::findGame(const char *gameId) { } bool GlulxeMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) { - const char *const EXTENSIONS[] = { ".ulx", ".blb", ".gblorb", nullptr }; + const char *const EXTENSIONS[] = { ".ulx", nullptr }; // Loop through the files of the folder for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { @@ -54,11 +55,9 @@ bool GlulxeMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames & if (file->isDirectory()) continue; Common::String filename = file->getName(); - bool hasExt = false; + bool hasExt = false, isBlorb = false; for (const char *const *ext = &EXTENSIONS[0]; *ext && !hasExt; ++ext) hasExt = filename.hasSuffixIgnoreCase(*ext); - if (!hasExt) - continue; // Open up the file and calculate the md5 Common::File gameFile; @@ -66,8 +65,13 @@ bool GlulxeMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames & continue; Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000); size_t filesize = gameFile.size(); + gameFile.seek(0); + isBlorb = Blorb::isBlorb(gameFile, ID_GLUL); gameFile.close(); + if (!hasExt && !isBlorb) + continue; + // Check for known games const GlulxeGameDescription *p = GLULXE_GAMES; while (p->_gameId && (md5 != p->_md5 || filesize != p->_filesize)) @@ -75,9 +79,6 @@ bool GlulxeMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames & DetectedGame gd; if (!p->_gameId) { - if (filename.hasSuffixIgnoreCase(".blb")) - continue; - if (gDebugLevel > 0) { // Print an entry suitable for putting into the detection_tables.h, using the // name of the parent folder the game is in as the presumed game Id diff --git a/engines/glk/magnetic/detection.cpp b/engines/glk/magnetic/detection.cpp index f7021065bc..cfd0102895 100644 --- a/engines/glk/magnetic/detection.cpp +++ b/engines/glk/magnetic/detection.cpp @@ -75,9 +75,6 @@ bool MagneticMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames DetectedGame gd; if (!p->_gameId) { - if (filename.hasSuffixIgnoreCase(".blb")) - continue; - if (gDebugLevel > 0) { // Print an entry suitable for putting into the detection_tables.h, using the // name of the parent folder the game is in as the presumed game Id diff --git a/engines/glk/scott/detection.cpp b/engines/glk/scott/detection.cpp index f1c4b9aa69..0e89b13f86 100644 --- a/engines/glk/scott/detection.cpp +++ b/engines/glk/scott/detection.cpp @@ -22,6 +22,7 @@ #include "glk/scott/detection.h" #include "glk/scott/detection_tables.h" +#include "glk/blorb.h" #include "common/file.h" #include "common/md5.h" #include "engines/game.h" @@ -44,41 +45,43 @@ GameDescriptor ScottMetaEngine::findGame(const char *gameId) { } bool ScottMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) { - const char *const EXTENSIONS[] = { ".saga", ".dat", ".blb", ".blorb", nullptr }; - Common::File gameFile; - Common::String md5; + const char *const EXTENSIONS[] = { ".saga", ".dat", nullptr }; // Loop through the files of the folder for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { - Common::String name = file->getName(); + // Check for a recognised filename if (file->isDirectory()) continue; Common::String filename = file->getName(); - bool hasExt = false; + bool hasExt = false, isBlorb = false; for (const char *const *ext = &EXTENSIONS[0]; *ext && !hasExt; ++ext) hasExt = filename.hasSuffixIgnoreCase(*ext); - if (!hasExt) - continue; - if (gameFile.open(*file)) { - md5 = Common::computeStreamMD5AsString(gameFile, 5000); + Common::File gameFile; + if (!gameFile.open(*file)) + continue; + Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000); + int32 filesize = gameFile.size(); + gameFile.seek(0); + isBlorb = Blorb::isBlorb(gameFile, ID_SAAI); + gameFile.close(); - // Scan through the Scott game list for a match - const ScottGame *p = SCOTT_GAMES; - while (p->_md5 && p->_filesize != gameFile.size() && md5 != p->_md5) - ++p; + if (!hasExt && !isBlorb) + continue; - if (p->_filesize) { - // Found a match - PlainGameDescriptor gameDesc = findGame(p->_gameId); - DetectedGame gd(p->_gameId, gameDesc.description, Common::EN_ANY, Common::kPlatformUnknown); - gd.addExtraEntry("filename", file->getName()); + // Scan through the Scott game list for a match + const ScottGame *p = SCOTT_GAMES; + while (p->_md5 && p->_filesize != filesize && md5 != p->_md5) + ++p; - gameList.push_back(gd); - } + if (p->_filesize) { + // Found a match + PlainGameDescriptor gameDesc = findGame(p->_gameId); + DetectedGame gd(p->_gameId, gameDesc.description, Common::EN_ANY, Common::kPlatformUnknown); + gd.addExtraEntry("filename", file->getName()); - gameFile.close(); + gameList.push_back(gd); } } diff --git a/engines/glk/tads/detection.cpp b/engines/glk/tads/detection.cpp index 9e1eb0833d..92633dacef 100644 --- a/engines/glk/tads/detection.cpp +++ b/engines/glk/tads/detection.cpp @@ -22,6 +22,7 @@ #include "glk/tads/detection.h" #include "glk/tads/detection_tables.h" +#include "glk/blorb.h" #include "common/debug.h" #include "common/file.h" #include "common/md5.h" @@ -58,22 +59,32 @@ GameDescriptor TADSMetaEngine::findGame(const char *gameId) { } bool TADSMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) { + const char *const EXTENSIONS[] = { ".gam", nullptr }; + // Loop through the files of the folder for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { // Check for a recognised filename - Common::String filename = file->getName(); - if (file->isDirectory() || !(filename.hasSuffixIgnoreCase(".gam") - || filename.hasSuffixIgnoreCase(".blorb"))) + if (file->isDirectory()) continue; + Common::String filename = file->getName(); + bool hasExt = false, isBlorb = false; + for (const char *const *ext = &EXTENSIONS[0]; *ext && !hasExt; ++ext) + hasExt = filename.hasSuffixIgnoreCase(*ext); + // Open up the file and calculate the md5 Common::File gameFile; if (!gameFile.open(*file)) continue; Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000); size_t filesize = gameFile.size(); + gameFile.seek(0); + isBlorb = Blorb::isBlorb(gameFile, ID_TAD2) || Blorb::isBlorb(gameFile, ID_TAD3); gameFile.close(); + if (!hasExt && !isBlorb) + continue; + // Check for known games const TADSGameDescription *p = TADS_GAMES; while (p->_gameId && p->_md5 && (md5 != p->_md5 || filesize != p->_filesize)) @@ -81,9 +92,6 @@ bool TADSMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &ga DetectedGame gd; if (!p->_gameId) { - if (!filename.hasSuffixIgnoreCase(".gam")) - continue; - if (gDebugLevel > 0) { // Print an entry suitable for putting into the detection_tables.h, using the Common::String fname = filename; -- cgit v1.2.3