bool GlulxeMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) { const char *const EXTENSIONS[3] = { ".ulx", ".blb", ".gblorb" }; // Loop through the files of the folder for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { // Check for a recognised filename if (file->isDirectory()) continue; Common::String filename = file->getName(); bool hasExt = false; for (int idx = 0; idx < 3 && !hasExt; ++idx) hasExt = filename.hasSuffixIgnoreCase(EXTENSIONS[idx]); if (!hasExt) continue; // 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.close(); // Check for known games const GlulxeGameDescription *p = GLULXE_GAMES; while (p->_gameId && (md5 != p->_md5 || filesize != p->_filesize)) ++p; 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 Common::String folderName = file->getParent().getName(); if (folderName.hasSuffix("\\")) folderName.deleteLastChar(); Common::String fname = filename; const char *dot = strchr(fname.c_str(), '.'); if (dot) fname = Common::String(fname.c_str(), dot); debug("ENTRY0(\"%s\", \"%s\", %u),", fname.c_str(), md5.c_str(), (uint)filesize); } const PlainGameDescriptor &desc = GLULXE_GAME_LIST[0]; gd = DetectedGame(desc.gameId, desc.description, Common::UNK_LANG, Common::kPlatformUnknown); } else { PlainGameDescriptor gameDesc = findGame(p->_gameId); gd = DetectedGame(p->_gameId, gameDesc.description, p->_language, Common::kPlatformUnknown, p->_extra); gd.setGUIOptions(GUIO4(GUIO_NOSPEECH, GUIO_NOSFX, GUIO_NOMUSIC, GUIO_NOSUBTITLES)); } gd.addExtraEntry("filename", filename); gameList.push_back(gd); } return !gameList.empty(); }
namespace Groovie { #define GAMEOPTION_T7G_FAST_MOVIE_SPEED GUIO_GAMEOPTIONS1 static const PlainGameDescriptor groovieGames[] = { // Games {"t7g", "The 7th Guest"}, #ifdef ENABLE_GROOVIE2 {"11h", "The 11th Hour: The Sequel to The 7th Guest"}, {"clandestiny", "Clandestiny"}, {"unclehenry", "Uncle Henry's Playhouse"}, {"tlc", "Tender Loving Care"}, #endif {0, 0} }; static const GroovieGameDescription gameDescriptions[] = { // The 7th Guest DOS English { { "t7g", "", AD_ENTRY1s("script.grv", "d1b8033b40aa67c076039881eccce90d", 16659), Common::EN_ANY, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO5(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT, GAMEOPTION_T7G_FAST_MOVIE_SPEED) }, kGroovieT7G, 0 }, // The 7th Guest Mac English { { "t7g", "", AD_ENTRY1s("T7GMac", "acdc4a58dd3f007f65e99b99d78e0bce", 1814029), Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK, GUIO5(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT, GAMEOPTION_T7G_FAST_MOVIE_SPEED) }, kGroovieT7G, 0 }, #if 0 // These entries should now be identical to the first T7G Mac entry after // changing the app to only use the data part of the resource fork. They // are left disabled here as a reference. // The 7th Guest Mac English (Aztec single disc) { { "t7g", "", AD_ENTRY1s("T7GMac", "6bdee8d0f9eef6d58d02fcd7deec3fb2", 1830783), Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK, GUIO5(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT, GAMEOPTION_T7G_FAST_MOVIE_SPEED) }, kGroovieT7G, 0 }, // The 7th Guest Mac English (Aztec bundle, provided by Thefinaleofseem) { { "t7g", "", AD_ENTRY1s("T7GMac", "0d595d4b44ae1814082938d051e5174e", 1830783), Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK, GUIO5(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT, GAMEOPTION_T7G_FAST_MOVIE_SPEED) }, kGroovieT7G, 0 }, #endif // The 7th Guest DOS Russian (Akella) { { "t7g", "", { { "script.grv", 0, "d1b8033b40aa67c076039881eccce90d", 16659}, { "intro.gjd", 0, NULL, 31711554}, { NULL, 0, NULL, 0} }, Common::RU_RUS, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO5(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT, GAMEOPTION_T7G_FAST_MOVIE_SPEED) }, kGroovieT7G, 0 }, { { "t7g", "", { { "script.grv", 0, "d1b8033b40aa67c076039881eccce90d", 16659}, { "SeventhGuest", 0, NULL, -1}, { NULL, 0, NULL, 0} }, Common::EN_ANY, Common::kPlatformIOS, ADGF_NO_FLAGS, GUIO3(GUIO_NOMIDI, GUIO_NOASPECT, GAMEOPTION_T7G_FAST_MOVIE_SPEED) }, kGroovieT7G, 0 }, #ifdef ENABLE_GROOVIE2 // The 11th Hour DOS English { { "11h", "", AD_ENTRY1s("disk.1", "5c0428cd3659fc7bbcd0aa16485ed5da", 227), Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT) }, kGroovieV2, 1 }, // The 11th Hour Macintosh English { { "11h", "", { { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 }, { "The 11th Hour Installer", 0, "bcdb4040b27f15b18f39fb9e496d384a", 1002987 }, { 0, 0, 0, 0 } }, Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE, GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT) }, kGroovieV2, 1 }, // The 11th Hour Macintosh English (Installed) { { "11h", "Installed", { { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 }, { "el01.mov", 0, "70f42dfc25b1488a08011dc45bb5145d", 6039 }, { 0, 0, 0, 0 } }, Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE, GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT) }, kGroovieV2, 1 }, // The 11th Hour DOS Demo English { { "11h", "Demo", AD_ENTRY1s("disk.1", "aacb32ce07e0df2894bd83a3dee40c12", 70), Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO | ADGF_UNSTABLE, GUIO5(GUIO_NOLAUNCHLOAD, GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT) }, kGroovieV2, 1 }, // The Making of The 11th Hour DOS English { { "11h", "Making Of", AD_ENTRY1s("disk.1", "5c0428cd3659fc7bbcd0aa16485ed5da", 227), Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO3(GUIO_NOMIDI, GUIO_NOLAUNCHLOAD, GUIO_NOASPECT) }, kGroovieV2, 2 }, // The Making of The 11th Hour Macintosh English { { "11h", "Making Of", { { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 }, { "The 11th Hour Installer", 0, "bcdb4040b27f15b18f39fb9e496d384a", 1002987 }, { 0, 0, 0, 0 } }, Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE, GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT) }, kGroovieV2, 2 }, // The Making of The 11th Hour Macintosh English (Installed) { { "11h", "Making Of (Installed)", { { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 }, { "el01.mov", 0, "70f42dfc25b1488a08011dc45bb5145d", 6039 }, { 0, 0, 0, 0 } }, Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE, GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT) }, kGroovieV2, 2 }, // Clandestiny Trailer DOS English { { "clandestiny", "Trailer", AD_ENTRY1s("disk.1", "5c0428cd3659fc7bbcd0aa16485ed5da", 227), Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO3(GUIO_NOMIDI, GUIO_NOLAUNCHLOAD, GUIO_NOASPECT) }, kGroovieV2, 3 }, // Clandestiny Trailer Macintosh English { { "clandestiny", "Trailer", { { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 }, { "The 11th Hour Installer", 0, "bcdb4040b27f15b18f39fb9e496d384a", 1002987 }, { 0, 0, 0, 0 } }, Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE, GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT) }, kGroovieV2, 3 }, // Clandestiny Trailer Macintosh English (Installed) { { "clandestiny", "Trailer (Installed)", { { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 }, { "el01.mov", 0, "70f42dfc25b1488a08011dc45bb5145d", 6039 }, { 0, 0, 0, 0 } }, Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE, GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT) }, kGroovieV2, 3 }, // Clandestiny DOS English { { "clandestiny", "", AD_ENTRY1s("disk.1", "f79fc1515174540fef6a34132efc4c53", 76), Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT) }, kGroovieV2, 1 }, // Uncle Henry's Playhouse PC English { { "unclehenry", "", AD_ENTRY1s("disk.1", "0e1b1d3cecc4fc7efa62a968844d1f7a", 72), Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT) }, kGroovieV2, 1 }, // Tender Loving Care PC English { { "tlc", "", AD_ENTRY1s("disk.1", "32a1afa68478f1f9d2b25eeea427f2e3", 84), Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT) }, kGroovieV2, 1 }, #endif {AD_TABLE_END_MARKER, kGroovieT7G, 0} }; static const char *directoryGlobs[] = { "MIDI", 0 }; static const ADExtraGuiOptionsMap optionsList[] = { { GAMEOPTION_T7G_FAST_MOVIE_SPEED, { _s("Fast movie speed"), _s("Play movies at an increased speed"), "fast_movie_speed", false } }, AD_EXTRA_GUI_OPTIONS_TERMINATOR }; class GroovieMetaEngine : public AdvancedMetaEngine { public: GroovieMetaEngine() : AdvancedMetaEngine(gameDescriptions, sizeof(GroovieGameDescription), groovieGames, optionsList) { // Use kADFlagUseExtraAsHint in order to distinguish the 11th hour from // its "Making of" as well as the Clandestiny Trailer; they all share // the same MD5. // TODO: Is this the only reason, or are there others (like the three // potentially sharing a single directory) ? In the former case, then // perhaps a better solution would be to add additional files // to the detection entries. In the latter case, this TODO should be // replaced with an according explanation. _flags = kADFlagUseExtraAsHint; _guioptions = GUIO3(GUIO_NOSUBTITLES, GUIO_NOSFX, GUIO_NOASPECT); // Need MIDI directory to detect 11H Mac Installed _maxScanDepth = 2; _directoryGlobs = directoryGlobs; } const char *getEngineID() const { return "groovie"; } const char *getName() const { return "Groovie"; } const char *getOriginalCopyright() const { return "Groovie Engine (C) 1990-1996 Trilobyte"; } bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const; bool hasFeature(MetaEngineFeature f) const; SaveStateList listSaves(const char *target) const; int getMaximumSaveSlot() const; void removeSaveState(const char *target, int slot) const; SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; }; bool GroovieMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const { if (gd) { *engine = new GroovieEngine(syst, (const GroovieGameDescription *)gd); } return gd != 0; } bool GroovieMetaEngine::hasFeature(MetaEngineFeature f) const { return (f == kSupportsListSaves) || (f == kSupportsLoadingDuringStartup) || (f == kSupportsDeleteSave) || (f == kSavesSupportMetaInfo); } SaveStateList GroovieMetaEngine::listSaves(const char *target) const { return SaveLoad::listValidSaves(target); } int GroovieMetaEngine::getMaximumSaveSlot() const { return SaveLoad::getMaximumSlot(); } void GroovieMetaEngine::removeSaveState(const char *target, int slot) const { if (!SaveLoad::isSlotValid(slot)) { // Invalid slot, do nothing return; } Common::String filename = SaveLoad::getSlotSaveName(target, slot); g_system->getSavefileManager()->removeSavefile(filename); } SaveStateDescriptor GroovieMetaEngine::querySaveMetaInfos(const char *target, int slot) const { SaveStateDescriptor desc; Common::InSaveFile *savefile = SaveLoad::openForLoading(target, slot, &desc); delete savefile; return desc; } } // End of namespace Groovie
const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { bool foundResMap = false; bool foundRes000 = false; // Set some defaults s_fallbackDesc.extra = ""; s_fallbackDesc.language = Common::EN_ANY; s_fallbackDesc.flags = ADGF_NO_FLAGS; s_fallbackDesc.platform = Common::kPlatformPC; // default to PC platform s_fallbackDesc.gameid = "sci"; s_fallbackDesc.guioptions = GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI); if (allFiles.contains("resource.map") || allFiles.contains("Data1") || allFiles.contains("resmap.001") || allFiles.contains("resmap.001")) { foundResMap = true; } // Determine if we got a CD version and set the CD flag accordingly, by checking for // resource.aud for SCI1.1 CD games, or audio001.002 for SCI1 CD games. We assume that // the file should be over 10MB, as it contains all the game speech and is usually // around 450MB+. The size check is for some floppy game versions like KQ6 floppy, which // also have a small resource.aud file if (allFiles.contains("resource.aud") || allFiles.contains("audio001.002")) { Common::FSNode file = allFiles.contains("resource.aud") ? allFiles["resource.aud"] : allFiles["audio001.002"]; Common::SeekableReadStream *tmpStream = file.createReadStream(); if (tmpStream->size() > 10 * 1024 * 1024) { // We got a CD version, so set the CD flag accordingly s_fallbackDesc.flags |= ADGF_CD; } delete tmpStream; } if (allFiles.contains("resource.000") || allFiles.contains("resource.001") || allFiles.contains("ressci.000") || allFiles.contains("ressci.001")) foundRes000 = true; // Data1 contains both map and volume for SCI1.1+ Mac games if (allFiles.contains("Data1")) { foundResMap = foundRes000 = true; s_fallbackDesc.platform = Common::kPlatformMacintosh; } // Determine the game platform // The existence of any of these files indicates an Amiga game if (allFiles.contains("9.pat") || allFiles.contains("spal") || allFiles.contains("patch.005") || allFiles.contains("bank.001")) s_fallbackDesc.platform = Common::kPlatformAmiga; // The existence of 7.pat or patch.200 indicates a Mac game if (allFiles.contains("7.pat") || allFiles.contains("patch.200")) s_fallbackDesc.platform = Common::kPlatformMacintosh; // The data files for Atari ST versions are the same as their DOS counterparts // If these files aren't found, it can't be SCI if (!foundResMap && !foundRes000) { return 0; } ResourceManager resMan; resMan.addAppropriateSources(fslist); resMan.init(true); // TODO: Add error handling. #ifndef ENABLE_SCI32 // Is SCI32 compiled in? If not, and this is a SCI32 game, // stop here if (getSciVersion() >= SCI_VERSION_2) { return (const ADGameDescription *)&s_fallbackDesc; } #endif ViewType gameViews = resMan.getViewType(); // Have we identified the game views? If not, stop here // Can't be SCI (or unsupported SCI views). Pinball Creep by sierra also uses resource.map/resource.000 files // but doesnt share sci format at all, if we dont return 0 here we will detect this game as SCI if (gameViews == kViewUnknown) { return 0; } // Set the platform to Amiga if the game is using Amiga views if (gameViews == kViewAmiga) s_fallbackDesc.platform = Common::kPlatformAmiga; // Determine the game id Common::String sierraGameId = resMan.findSierraGameId(); // If we don't have a game id, the game is not SCI if (sierraGameId.empty()) { return 0; } Common::String gameId = convertSierraGameId(sierraGameId, &s_fallbackDesc.flags, resMan); strncpy(s_fallbackGameIdBuf, gameId.c_str(), sizeof(s_fallbackGameIdBuf) - 1); s_fallbackGameIdBuf[sizeof(s_fallbackGameIdBuf) - 1] = 0; // Make sure string is NULL terminated s_fallbackDesc.gameid = s_fallbackGameIdBuf; // Try to determine the game language // Load up text 0 and start looking for "#" characters // Non-English versions contain strings like XXXX#YZZZZ // Where XXXX is the English string, #Y a separator indicating the language // (e.g. #G for German) and ZZZZ is the translated text // NOTE: This doesn't work for games which use message instead of text resources // (like, for example, Eco Quest 1 and all SCI1.1 games and newer, e.g. Freddy Pharkas). // As far as we know, these games store the messages of each language in separate // resources, and it's not possible to detect that easily // Also look for "%J" which is used in japanese games Resource *text = resMan.findResource(ResourceId(kResourceTypeText, 0), 0); uint seeker = 0; if (text) { while (seeker < text->size) { if (text->data[seeker] == '#') { if (seeker + 1 < text->size) s_fallbackDesc.language = charToScummVMLanguage(text->data[seeker + 1]); break; } if (text->data[seeker] == '%') { if ((seeker + 1 < text->size) && (text->data[seeker + 1] == 'J')) { s_fallbackDesc.language = charToScummVMLanguage(text->data[seeker + 1]); break; } } seeker++; } } // Fill in "extra" field // Is this an EGA version that might have a VGA pendant? Then we want // to mark it as such in the "extra" field. const bool markAsEGA = (gameViews == kViewEga && s_fallbackDesc.platform != Common::kPlatformAmiga && getSciVersion() > SCI_VERSION_1_EGA_ONLY); const bool isDemo = (s_fallbackDesc.flags & ADGF_DEMO); const bool isCD = (s_fallbackDesc.flags & ADGF_CD); if (!isCD) s_fallbackDesc.guioptions = GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI); if (gameId.hasSuffix("sci")) { s_fallbackDesc.extra = "SCI"; // Differentiate EGA versions from the VGA ones, where needed if (markAsEGA) s_fallbackDesc.extra = "SCI/EGA"; // Mark as demo. // Note: This overwrites the 'EGA' info, if it was previously set. if (isDemo) s_fallbackDesc.extra = "SCI/Demo"; } else { if (markAsEGA) s_fallbackDesc.extra = "EGA"; // Set "CD" and "Demo" as appropriate. // Note: This overwrites the 'EGA' info, if it was previously set. if (isDemo && isCD) s_fallbackDesc.extra = "CD Demo"; else if (isDemo) s_fallbackDesc.extra = "Demo"; else if (isCD) s_fallbackDesc.extra = "CD"; } return &s_fallbackDesc; }