GameList QueenMetaEngine::detectGames(const Common::FSList &fslist) const { GameList detectedGames; // Iterate over all files in the given directory for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { if (file->isDirectory()) { continue; } if (file->getName().equalsIgnoreCase("queen.1") || file->getName().equalsIgnoreCase("queen.1c")) { Common::File dataFile; if (!dataFile.open(*file)) { continue; } Queen::DetectedGameVersion version; if (Queen::Resource::detectVersion(&version, &dataFile)) { GameDescriptor dg(queenGameDescriptor.gameid, queenGameDescriptor.description, version.language, version.platform); if (version.features & Queen::GF_DEMO) { dg.updateDesc("Demo"); dg.setGUIOptions(Common::GUIO_NOSPEECH); } else if (version.features & Queen::GF_INTERVIEW) { dg.updateDesc("Interview"); dg.setGUIOptions(Common::GUIO_NOSPEECH); } else if (version.features & Queen::GF_FLOPPY) { dg.updateDesc("Floppy"); dg.setGUIOptions(Common::GUIO_NOSPEECH); } else if (version.features & Queen::GF_TALKIE) { dg.updateDesc("Talkie"); } detectedGames.push_back(dg); break; } } } return detectedGames; }
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(); }
static void composeFileHashMap(const Common::FSList &fslist, DescMap &fileMD5Map, int depth, const char **globs) { if (depth <= 0) return; if (fslist.empty()) return; for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { if (!file->isDirectory()) { DetectorDesc d; d.node = *file; d.md5Entry = 0; fileMD5Map[file->getName()] = d; } else { if (!globs) continue; bool matched = false; for (const char **glob = globs; *glob; glob++) if (file->getName().matchString(*glob, true)) { matched = true; break; } if (!matched) continue; Common::FSList files; if (file->getChildren(files, Common::FSNode::kListAll)) { composeFileHashMap(files, fileMD5Map, depth - 1, globs); } } } }
static int findGames(Game *games, int max) { Dir *dirs = new Dir[MAX_DIR]; int curr_game = 0, curr_dir = 0, num_dirs = 1; dirs[0].node = Common::FSNode(""); while (curr_game < max && curr_dir < num_dirs) { strncpy(dirs[curr_dir].name, dirs[curr_dir].node.getPath().c_str(), 252); dirs[curr_dir].name[251] = '\0'; dirs[curr_dir].deficon[0] = '\0'; Common::FSList files, fslist; dirs[curr_dir++].node.getChildren(fslist, Common::FSNode::kListAll); for (Common::FSList::const_iterator entry = fslist.begin(); entry != fslist.end(); ++entry) { if (entry->isDirectory()) { if (num_dirs < MAX_DIR && strcasecmp(entry->getDisplayName().c_str(), "install")) { dirs[num_dirs].node = *entry; num_dirs++; } } else if (isIcon(*entry)) strcpy(dirs[curr_dir-1].deficon, entry->getDisplayName().c_str()); else files.push_back(*entry); } GameList candidates = EngineMan.detectGames(files); for (GameList::const_iterator ge = candidates.begin(); ge != candidates.end(); ++ge) if (curr_game < max) { strcpy(games[curr_game].filename_base, ge->gameid().c_str()); strcpy(games[curr_game].dir, dirs[curr_dir-1].name); games[curr_game].language = ge->language(); games[curr_game].platform = ge->platform(); if (uniqueGame(games[curr_game].filename_base, games[curr_game].dir, games[curr_game].language, games[curr_game].platform, games, curr_game)) { strcpy(games[curr_game].text, ge->description().c_str()); #if 0 printf("Registered game <%s> (l:%d p:%d) in <%s> <%s> because of <%s> <*>\n", games[curr_game].text, (int)games[curr_game].language, (int)games[curr_game].platform, games[curr_game].dir, games[curr_game].filename_base, dirs[curr_dir-1].name); #endif curr_game++; } } } for (int i=0; i<curr_game; i++) if (!loadIcon(games[i], dirs, num_dirs)) makeDefIcon(games[i].icon); delete[] dirs; return curr_game; }
// Search for a node with the given "name", inside fslist. Ignores case // when performing the matching. The first match is returned, so if you // search for "resource" and two nodes "RESOURE and "resource" are present, // the first match is used. static bool searchFSNode(const Common::FSList &fslist, const Common::String &name, Common::FSNode &result) { for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { if (!scumm_stricmp(file->getName().c_str(), name.c_str())) { result = *file; return true; } } return false; }
bool BaseFileManager::registerPackages(const Common::FSList &fslist) { for (Common::FSList::const_iterator it = fslist.begin(); it != fslist.end(); ++it) { debugC(kWintermuteDebugFileAccess, "Adding %s", it->getName().c_str()); if (it->getName().contains(".dcp")) { if (registerPackage(*it)) { addPath(PATH_PACKAGE, *it); } } } return true; }
bool isFullGame(const Common::FSList &fslist) { Common::FSList::const_iterator file; // We distinguish between the two versions by the presense of paris.clu for (file = fslist.begin(); file != fslist.end(); ++file) { if (!file->isDirectory()) { if (file->getName().equalsIgnoreCase("paris.clu")) return true; } } return false; }
bool TADSMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) { // 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"))) 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 TADSGameDescription *p = TADS_GAMES; while (p->_gameId && p->_md5 && (md5 != p->_md5 || filesize != p->_filesize)) ++p; 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; 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 TADSDescriptor &desc = TADS_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.addExtraEntry("filename", filename); gameList.push_back(gd); } return !gameList.empty(); }
GameList SkyMetaEngine::detectGames(const Common::FSList &fslist) const { GameList detectedGames; bool hasSkyDsk = false; bool hasSkyDnr = false; int dinnerTableEntries = -1; int dataDiskSize = -1; // Iterate over all files in the given directory for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { if (!file->isDirectory()) { const char *fileName = file->getName().c_str(); if (0 == scumm_stricmp("sky.dsk", fileName)) { Common::File dataDisk; if (dataDisk.open(*file)) { hasSkyDsk = true; dataDiskSize = dataDisk.size(); } } if (0 == scumm_stricmp("sky.dnr", fileName)) { Common::File dinner; if (dinner.open(*file)) { hasSkyDnr = true; dinnerTableEntries = dinner.readUint32LE(); } } } } if (hasSkyDsk && hasSkyDnr) { // Match found, add to list of candidates, then abort inner loop. // The game detector uses US English by default. We want British // English to match the recorded voices better. GameDescriptor dg("sky", skySetting.gameid, skySetting.description, Common::UNK_LANG, Common::kPlatformUnknown); const SkyVersion *sv = skyVersions; while (sv->dinnerTableEntries) { if (dinnerTableEntries == sv->dinnerTableEntries && (sv->dataDiskSize == dataDiskSize || sv->dataDiskSize == -1)) { dg.updateDesc(Common::String::format("v0.0%d %s", sv->version, sv->extraDesc).c_str()); dg.setGUIOptions(sv->guioptions); break; } ++sv; } detectedGames.push_back(dg); } return detectedGames; }
void Sword1CheckDirectory(const Common::FSList &fslist, bool *filesFound) { for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { if (!file->isDirectory()) { const char *fileName = file->getName().c_str(); for (int cnt = 0; cnt < NUM_FILES_TO_CHECK; cnt++) if (scumm_stricmp(fileName, g_filesToCheck[cnt]) == 0) filesFound[cnt] = true; } else { for (int cnt = 0; cnt < ARRAYSIZE(g_dirNames); cnt++) if (scumm_stricmp(file->getName().c_str(), g_dirNames[cnt]) == 0) { Common::FSList fslist2; if (file->getChildren(fslist2, Common::FSNode::kListFilesOnly)) Sword1CheckDirectory(fslist2, filesFound); } } } }
void DefaultSaveFileManager::assureCached(const Common::String &savePathName) { // Check that path exists and is usable. checkPath(Common::FSNode(savePathName)); #ifdef USE_LIBCURL Common::Array<Common::String> files = CloudMan.getSyncingFiles(); //returns empty array if not syncing if (!files.empty()) updateSavefilesList(files); //makes this cache invalid else _lockedFiles = files; #endif if (_cachedDirectory == savePathName) { return; } _saveFileCache.clear(); _cachedDirectory.clear(); if (getError().getCode() != Common::kNoError) { warning("DefaultSaveFileManager::assureCached: Can not cache path '%s': '%s'", savePathName.c_str(), getErrorDesc().c_str()); return; } // FSNode can cache its members, thus create it after checkPath to reflect // actual file system state. const Common::FSNode savePath(savePathName); Common::FSList children; if (!savePath.getChildren(children, Common::FSNode::kListFilesOnly)) { return; } // Build the savefile name cache. for (Common::FSList::const_iterator file = children.begin(), end = children.end(); file != end; ++file) { if (_saveFileCache.contains(file->getName())) { warning("DefaultSaveFileManager::assureCached: Name clash when building cache, ignoring file '%s'", file->getName().c_str()); } else { _saveFileCache[file->getName()] = *file; } } // Only now store that we cached 'savePathName' to indicate we successfully // cached the directory. _cachedDirectory = savePathName; }
static void composeFileHashMap(const Common::FSList &fslist, FileMap &allFiles, int depth, const char * const *directoryGlobs) { if (depth <= 0) return; if (fslist.empty()) return; // First we compose a hashmap of all files in fslist. // Includes nifty stuff like removing trailing dots and ignoring case. for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { if (file->isDirectory()) { Common::FSList files; if (!directoryGlobs) continue; bool matched = false; for (const char * const *glob = directoryGlobs; *glob; glob++) if (file->getName().matchString(*glob, true)) { matched = true; break; } if (!matched) continue; if (!file->getChildren(files, Common::FSNode::kListAll)) continue; composeFileHashMap(files, allFiles, depth - 1, directoryGlobs); } Common::String tstr = file->getName(); // Strip any trailing dot if (tstr.lastChar() == '.') tstr.deleteLastChar(); allFiles[tstr] = *file; // Record the presence of this file } }
void Sword1CheckDirectory(const Common::FSList &fslist, bool *filesFound, bool recursion = false) { for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { if (!file->isDirectory()) { // The required game data files can be located in the game directory, or in // a subdirectory called "clusters". In the latter case, we don't want to // detect the game in that subdirectory, as this will detect the game twice // when mass add is searching inside a directory. In this case, the first // result (the game directory) will be correct, but the second result (the // clusters subdirectory) will be wrong, as the optional speech, music and // video data files will be ignored. Note that this fix will skip the game // data files if the user has placed them inside a "clusters" subdirectory, // or if he/she points ScummVM directly to the "clusters" directory of the // game CD. Fixes bug #3049346. Common::String directory = file->getParent().getName(); directory.toLowercase(); if (directory.hasPrefix("clusters") && directory.size() <= 9 && !recursion) continue; const char *fileName = file->getName().c_str(); for (int cnt = 0; cnt < NUM_FILES_TO_CHECK; cnt++) if (scumm_stricmp(fileName, g_filesToCheck[cnt]) == 0) filesFound[cnt] = true; } else { for (int cnt = 0; cnt < ARRAYSIZE(g_dirNames); cnt++) if (scumm_stricmp(file->getName().c_str(), g_dirNames[cnt]) == 0) { Common::FSList fslist2; if (file->getChildren(fslist2, Common::FSNode::kListFilesOnly)) Sword1CheckDirectory(fslist2, filesFound, true); } } } }
void AdvancedMetaEngine::composeFileHashMap(FileMap &allFiles, const Common::FSList &fslist, int depth, const Common::String &parentName) const { if (depth <= 0) return; if (fslist.empty()) return; for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { Common::String tstr = (_matchFullPaths && !parentName.empty() ? parentName + "/" : "") + file->getName(); if (file->isDirectory()) { Common::FSList files; if (!_directoryGlobs) continue; bool matched = false; for (const char * const *glob = _directoryGlobs; *glob; glob++) if (file->getName().matchString(*glob, true)) { matched = true; break; } if (!matched) continue; if (!file->getChildren(files, Common::FSNode::kListAll)) continue; composeFileHashMap(allFiles, files, depth - 1, tstr); } // Strip any trailing dot if (tstr.lastChar() == '.') tstr.deleteLastChar(); allFiles[tstr] = *file; // Record the presence of this file } }
Common::HashMap<Common::String, Score *> DirectorEngine::loadMMMNames(Common::String folder) { Common::FSNode directory(folder); Common::FSList movies; Common::HashMap<Common::String, Score *> nameMap; directory.getChildren(movies, Common::FSNode::kListFilesOnly); if (!movies.empty()) { for (Common::FSList::const_iterator i = movies.begin(); i != movies.end(); ++i) { if (i->getName() == _sharedMMM) { loadSharedCastsFrom(i->getPath()); continue; } RIFFArchive *arc = new RIFFArchive(); arc->openFile(i->getPath()); Score *sc = new Score(this); nameMap[sc->getMacName()] = sc; } } return nameMap; }
virtual const ADGameDescription *fallbackDetect(const Common::FSList &fslist) const { for (Common::FSList::const_iterator d = fslist.begin(); d != fslist.end(); ++d) { Common::FSList audiofslist; if (d->isDirectory() && d->getName().equalsIgnoreCase("audio") && d->getChildren(audiofslist, Common::FSNode::kListFilesOnly)) { for (Common::FSList::const_iterator f = audiofslist.begin(); f != audiofslist.end(); ++f) { if (!f->isDirectory() && f->getName().equalsIgnoreCase("demorolc.raw")) { return &tuckerDemoGameDescription; } } } } return 0; }
GameList Sword2MetaEngine::detectGames(const Common::FSList &fslist) const { GameList detectedGames; const Sword2::GameSettings *g; Common::FSList::const_iterator file; // TODO: It would be nice if we had code here which distinguishes // between the 'sword2' and 'sword2demo' targets. The current code // can't do that since they use the same detectname. for (g = Sword2::sword2_settings; g->gameid; ++g) { // Iterate over all files in the given directory for (file = fslist.begin(); file != fslist.end(); ++file) { if (!file->isDirectory()) { const char *fileName = file->getName().c_str(); if (0 == scumm_stricmp(g->detectname, fileName)) { // Match found, add to list of candidates, then abort inner loop. detectedGames.push_back(GameDescriptor(g->gameid, g->description, Common::UNK_LANG, Common::kPlatformUnknown, Common::GUIO_NOMIDI)); break; } } } } if (detectedGames.empty()) { // Nothing found -- try to recurse into the 'clusters' subdirectory, // present e.g. if the user copied the data straight from CD. for (file = fslist.begin(); file != fslist.end(); ++file) { if (file->isDirectory()) { const char *fileName = file->getName().c_str(); if (0 == scumm_stricmp("clusters", fileName)) { Common::FSList recList; if (file->getChildren(recList, Common::FSNode::kListAll)) { GameList recGames(detectGames(recList)); if (!recGames.empty()) { detectedGames.push_back(recGames); break; } } } } } } return detectedGames; }
bool Sword25Engine::loadPackages() { PackageManager *packageManagerPtr = Kernel::getInstance()->getPackage(); assert(packageManagerPtr); // Load the main package if (!packageManagerPtr->loadPackage("data.b25c", "/")) return false; // Get the contents of the main program directory and sort them alphabetically Common::FSNode dir(ConfMan.get("path")); Common::FSList files; if (!dir.isDirectory() || !dir.getChildren(files, Common::FSNode::kListAll)) { warning("Game data path does not exist or is not a directory"); return false; } Common::sort(files.begin(), files.end()); // Identify all patch packages // The filename of patch packages must have the form patch??.b25c, with the question marks // are placeholders for numbers. // Since the filenames have been sorted, patches are mounted with low numbers first, through // to ones with high numbers. This is important, because newly mount packages overwrite // existing files in the virtual file system, if they include files with the same name. for (Common::FSList::const_iterator it = files.begin(); it != files.end(); ++it) { if (it->getName().matchString("patch???.b25c", true)) if (!packageManagerPtr->loadPackage(it->getName(), "/")) return false; } // Identify and mount all language packages // The filename of the packages have the form lang_*.b25c (eg. lang_de.b25c) for (Common::FSList::const_iterator it = files.begin(); it != files.end(); ++it) { if (it->getName().matchString("lang_*.b25c", true)) if (!packageManagerPtr->loadPackage(it->getName(), "/")) return false; } return true; }
Common::HashMap<Common::String, Score *> *DirectorEngine::scanMovies(const Common::String &folder) { Common::FSNode directory(folder); Common::FSList movies; const char *sharedMMMname; if (getPlatform() == Common::kPlatformWindows) sharedMMMname = "SHARDCST.MMM"; else sharedMMMname = "Shared Cast*"; Common::HashMap<Common::String, Score *> *nameMap = new Common::HashMap<Common::String, Score *>(); if (!directory.getChildren(movies, Common::FSNode::kListFilesOnly)) return nameMap; if (!movies.empty()) { for (Common::FSList::const_iterator i = movies.begin(); i != movies.end(); ++i) { debugC(2, kDebugLoading, "File: %s", i->getName().c_str()); if (Common::matchString(i->getName().c_str(), sharedMMMname, true)) { _sharedCastFile = i->getName(); continue; } Archive *arc = createArchive(); warning("name: %s", i->getName().c_str()); arc->openFile(i->getName()); Score *sc = new Score(this, arc); nameMap->setVal(sc->getMacName(), sc); debugC(2, kDebugLoading, "Movie name: \"%s\"", sc->getMacName().c_str()); } } return nameMap; }
bool BaseFileManager::registerPackages() { debugC(kWintermuteDebugFileAccess | kWintermuteDebugLog, "Scanning packages"); // We need the target name as a Common::String to perform some game-specific hacks. Common::String targetName = BaseEngine::instance().getGameTargetName(); // Register without using SearchMan, as otherwise the FSNode-based lookup in openPackage will fail // and that has to be like that to support the detection-scheme. Common::FSList files; for (Common::FSList::const_iterator it = _packagePaths.begin(); it != _packagePaths.end(); ++it) { debugC(kWintermuteDebugFileAccess, "Should register folder: %s %s", it->getPath().c_str(), it->getName().c_str()); if (!it->getChildren(files, Common::FSNode::kListFilesOnly)) { warning("getChildren() failed for path: %s", it->getDisplayName().c_str()); } for (Common::FSList::const_iterator fileIt = files.begin(); fileIt != files.end(); ++fileIt) { // To prevent any case sensitivity issues we make the filename // all lowercase here. This makes the code slightly prettier // than the equivalent of using equalsIgnoreCase. Common::String fileName = fileIt->getName(); fileName.toLowercase(); if (!fileName.hasSuffix(".dcp")) { continue; } // HACK: for Reversion1, avoid loading xlanguage_pt.dcp from the main folder: if (_language != Common::PT_BRA && targetName.hasPrefix("reversion1")) { if (fileName == "xlanguage_pt.dcp") { continue; } } // Again, make the parent's name all lowercase to avoid any case // issues. Common::String parentName = fileIt->getParent().getName(); parentName.toLowercase(); // Avoid registering all the language files // TODO: Select based on the gameDesc. if (_language != Common::UNK_LANG && (parentName == "language" || parentName == "languages")) { // English if (_language == Common::EN_ANY && (fileName != "english.dcp" && fileName != "xlanguage_en.dcp")) { continue; // Chinese } else if (_language == Common::ZH_CNA && (fileName != "chinese.dcp" && fileName != "xlanguage_nz.dcp")) { continue; // Czech } else if (_language == Common::CZ_CZE && (fileName != "czech.dcp" && fileName != "xlanguage_cz.dcp")) { continue; // French } else if (_language == Common::FR_FRA && (fileName != "french.dcp" && fileName != "xlanguage_fr.dcp")) { continue; // German } else if (_language == Common::DE_DEU && (fileName != "german.dcp" && fileName != "xlanguage_de.dcp")) { continue; // Italian } else if (_language == Common::IT_ITA && (fileName != "italian.dcp" && fileName != "xlanguage_it.dcp")) { continue; // Latvian } else if (_language == Common::LV_LAT && (fileName != "latvian.dcp" && fileName != "xlanguage_lv.dcp")) { // TODO: 'latvian.dcp' is just guesswork. Is there any // game using Latvian and using this filename? continue; // Polish } else if (_language == Common::PL_POL && (fileName != "polish.dcp" && fileName != "xlanguage_pl.dcp")) { continue; // Portuguese } else if (_language == Common::PT_BRA && (fileName != "portuguese.dcp" && fileName != "xlanguage_pt.dcp")) { continue; // Russian } else if (_language == Common::RU_RUS && (fileName != "russian.dcp" && fileName != "xlanguage_ru.dcp")) { continue; } } debugC(kWintermuteDebugFileAccess, "Registering %s %s", fileIt->getPath().c_str(), fileIt->getName().c_str()); registerPackage((*fileIt)); } } // debugC(kWintermuteDebugFileAccess | kWintermuteDebugLog, " Registered %d files in %d package(s)", _files.size(), _packages.size()); return STATUS_OK; }
bool FrotzMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) { const char *const EXTENSIONS[] = { ".z1", ".z2", ".z3", ".z4", ".z5", ".z6", ".z7", ".z8", ".dat", ".zip", 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 if (file->isDirectory()) continue; Common::String filename = file->getName(); bool hasExt = Blorb::hasBlorbExt(filename), 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; if (!gameFile.open(*file)) continue; Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000); size_t filesize = gameFile.size(); char serial[9] = ""; bool emptyBlorb = false; gameFile.seek(0); isBlorb = Blorb::isBlorb(gameFile, ID_ZCOD); if (!isBlorb) { if (Blorb::hasBlorbExt(filename)) { gameFile.close(); continue; } gameFile.seek(18); strcpy(&serial[0], "\""); gameFile.read(&serial[1], 6); strcpy(&serial[7], "\""); } else { Blorb b(*file, INTERPRETER_FROTZ); Common::SeekableReadStream *f = b.createReadStreamForMember("game"); emptyBlorb = f == nullptr; if (!emptyBlorb) { f->seek(18); strcpy(&serial[0], "\""); f->read(&serial[1], 6); strcpy(&serial[7], "\""); 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 && isBlorb))) ++p; DetectedGame gd; if (!p->_gameId) { // Generic .dat/.zip files don't get reported as matches unless they have a known md5 if (filename.hasSuffixIgnoreCase(".dat") || filename.hasSuffixIgnoreCase(".zip") || emptyBlorb) 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, \"%s\", %u),", fname.c_str(), strlen(serial) ? serial : "nullptr", md5.c_str(), (uint)filesize); } const PlainGameDescriptor &desc = ZCODE_GAME_LIST[0]; gd = DetectedGame(desc.gameId, desc.description, Common::UNK_LANG, Common::kPlatformUnknown); } else { GameDescriptor gameDesc = findGame(p->_gameId); gd = DetectedGame(p->_gameId, gameDesc._description, p->_language, Common::kPlatformUnknown, p->_extra); gd.setGUIOptions(p->_guiOptions); } gd.addExtraEntry("filename", filename); gameList.push_back(gd); } return !gameList.empty(); }
void MassAddDialog::handleTickle() { if (_scanStack.empty()) return; // We have finished scanning uint32 t = g_system->getMillis(); // Perform a breadth-first scan of the filesystem. while (!_scanStack.empty() && (g_system->getMillis() - t) < kMaxScanTime) { Common::FSNode dir = _scanStack.pop(); Common::FSList files; if (!dir.getChildren(files, Common::FSNode::kListAll)) { continue; } // Run the detector on the dir GameList candidates(EngineMan.detectGames(files)); // Just add all detected games / game variants. If we get more than one, // that either means the directory contains multiple games, or the detector // could not fully determine which game variant it was seeing. In either // case, let the user choose which entries he wants to keep. // // However, we only add games which are not already in the config file. for (GameList::const_iterator cand = candidates.begin(); cand != candidates.end(); ++cand) { GameDescriptor result = *cand; Common::String path = dir.getPath(); // Remove trailing slashes while (path != "/" && path.lastChar() == '/') path.deleteLastChar(); // Check for existing config entries for this path/gameid/lang/platform combination if (_pathToTargets.contains(path)) { bool duplicate = false; const StringArray &targets = _pathToTargets[path]; for (StringArray::const_iterator iter = targets.begin(); iter != targets.end(); ++iter) { // If the gameid, platform and language match -> skip it Common::ConfigManager::Domain *dom = ConfMan.getDomain(*iter); assert(dom); if ((*dom)["gameid"] == result["gameid"] && (*dom)["platform"] == result["platform"] && (*dom)["language"] == result["language"]) { duplicate = true; break; } } if (duplicate) { _oldGamesCount++; break; // Skip duplicates } } result["path"] = path; _games.push_back(result); _list->append(result.description()); } // Recurse into all subdirs for (Common::FSList::const_iterator file = files.begin(); file != files.end(); ++file) { if (file->isDirectory()) { _scanStack.push(*file); } } _dirsScanned++; } // Update the dialog Common::String buf; if (_scanStack.empty()) { // Enable the OK button _okButton->setEnabled(true); buf = _("Scan complete!"); _dirProgressText->setLabel(buf); buf = Common::String::format(_("Discovered %d new games, ignored %d previously added games."), _games.size(), _oldGamesCount); _gameProgressText->setLabel(buf); } else { buf = Common::String::format(_("Scanned %d directories ..."), _dirsScanned); _dirProgressText->setLabel(buf); buf = Common::String::format(_("Discovered %d new games, ignored %d previously added games ..."), _games.size(), _oldGamesCount); _gameProgressText->setLabel(buf); } if (_games.size() > 0) { _list->scrollToEnd(); } drawDialog(); }
GameList detectGamesImpl(const Common::FSList &fslist, bool recursion = false) { GameList detectedGames; const Sword2::GameSettings *g; Common::FSList::const_iterator file; bool isFullVersion = isFullGame(fslist); for (g = Sword2::sword2_settings; g->gameid; ++g) { // Iterate over all files in the given directory for (file = fslist.begin(); file != fslist.end(); ++file) { if (!file->isDirectory()) { // The required game data files can be located in the game directory, or in // a subdirectory called "clusters". In the latter case, we don't want to // detect the game in that subdirectory, as this will detect the game twice // when mass add is searching inside a directory. In this case, the first // result (the game directory) will be correct, but the second result (the // clusters subdirectory) will be wrong, as the optional speech, music and // video data files will be ignored. Note that this fix will skip the game // data files if the user has placed them inside a "clusters" subdirectory, // or if he/she points ScummVM directly to the "clusters" directory of the // game CD. Fixes bug #3049336. Common::String directory = file->getParent().getName(); directory.toLowercase(); if (directory.hasPrefix("clusters") && directory.size() <= 9 && !recursion) continue; if (file->getName().equalsIgnoreCase(g->detectname)) { // Make sure that the sword2 demo is not mixed up with the // full version, since they use the same filename for detection if ((g->features == Sword2::GF_DEMO && isFullVersion) || (g->features == 0 && !isFullVersion)) continue; // Match found, add to list of candidates, then abort inner loop. detectedGames.push_back(GameDescriptor(g->gameid, g->description, Common::UNK_LANG, Common::kPlatformUnknown, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT))); break; } } } } if (detectedGames.empty()) { // Nothing found -- try to recurse into the 'clusters' subdirectory, // present e.g. if the user copied the data straight from CD. for (file = fslist.begin(); file != fslist.end(); ++file) { if (file->isDirectory()) { if (file->getName().equalsIgnoreCase("clusters")) { Common::FSList recList; if (file->getChildren(recList, Common::FSNode::kListAll)) { GameList recGames(detectGamesImpl(recList, true)); if (!recGames.empty()) { detectedGames.push_back(recGames); break; } } } } } } return detectedGames; }
/** * Fallback detection scans the list of Discworld 2 targets to see if it can detect an installation * where the files haven't been renamed (i.e. don't have the '1' just before the extension) */ const ADGameDescription *TinselMetaEngine::fallbackDetect(const Common::FSList &fslist) const { Common::String extra; FileMap allFiles; SizeMD5Map filesSizeMD5; const ADGameFileDescription *fileDesc; const Tinsel::TinselGameDescription *g; if (fslist.empty()) return NULL; // TODO: The following code is essentially a slightly modified copy of the // complete code of function detectGame() in engines/advancedDetector.cpp. // That quite some hefty and undesirable code duplication. Its only purpose // seems to be to treat filenames of the form "foo1.ext" as "foo.ext". // It would be nice to avoid this code duplication. // First we compose a hashmap of all files in fslist. for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { if (file->isDirectory()) { if (!scumm_stricmp(file->getName().c_str(), "dw2")) { // Probably Discworld 2 subfolder on CD, so add it's contents as well Common::FSList files; if (file->getChildren(files, Common::FSNode::kListAll)) { Common::FSList::const_iterator file2; for (file2 = files.begin(); file2 != files.end(); ++file2) { if (file2->isDirectory()) continue; Common::String fname = file2->getName(); allFiles[fname] = *file2; } } } continue; } Common::String tstr = file->getName(); allFiles[tstr] = *file; // Record the presence of this file } // Check which files are included in some dw2 ADGameDescription *and* present // in fslist without a '1' suffix character. Compute MD5s and file sizes for these files. for (g = &Tinsel::gameDescriptions[0]; g->desc.gameid != 0; ++g) { if (strcmp(g->desc.gameid, "dw2") != 0) continue; for (fileDesc = g->desc.filesDescriptions; fileDesc->fileName; fileDesc++) { // Get the next filename, stripping off any '1' suffix character char tempFilename[50]; strcpy(tempFilename, fileDesc->fileName); char *pOne = strchr(tempFilename, '1'); if (pOne) { do { *pOne = *(pOne + 1); pOne++; } while (*pOne); } Common::String fname(tempFilename); if (allFiles.contains(fname) && !filesSizeMD5.contains(fname)) { SizeMD5 tmp; Common::File testFile; if (testFile.open(allFiles[fname])) { tmp.size = (int32)testFile.size(); tmp.md5 = computeStreamMD5AsString(testFile, detectionParams.md5Bytes); } else { tmp.size = -1; } filesSizeMD5[fname] = tmp; } } } ADGameDescList matched; int maxFilesMatched = 0; // MD5 based matching for (g = &Tinsel::gameDescriptions[0]; g->desc.gameid != 0; ++g) { if (strcmp(g->desc.gameid, "dw2") != 0) continue; bool fileMissing = false; if ((detectionParams.flags & kADFlagUseExtraAsHint) && !extra.empty() && g->desc.extra != extra) continue; bool allFilesPresent = true; // Try to match all files for this game for (fileDesc = g->desc.filesDescriptions; fileDesc->fileName; fileDesc++) { // Get the next filename, stripping off any '1' suffix character char tempFilename[50]; strcpy(tempFilename, fileDesc->fileName); char *pOne = strchr(tempFilename, '1'); if (pOne) { do { *pOne = *(pOne + 1); pOne++; } while (*pOne); } Common::String tstr(tempFilename); if (!filesSizeMD5.contains(tstr)) { fileMissing = true; allFilesPresent = false; break; } if (fileDesc->md5 != NULL && fileDesc->md5 != filesSizeMD5[tstr].md5) { fileMissing = true; break; } if (fileDesc->fileSize != -1 && fileDesc->fileSize != filesSizeMD5[tstr].size) { fileMissing = true; break; } } if (!fileMissing) { // Count the number of matching files. Then, only keep those // entries which match a maximal amount of files. int curFilesMatched = 0; for (fileDesc = g->desc.filesDescriptions; fileDesc->fileName; fileDesc++) curFilesMatched++; if (curFilesMatched > maxFilesMatched) { maxFilesMatched = curFilesMatched; matched.clear(); // Remove any prior, lower ranked matches. matched.push_back((const ADGameDescription *)g); } else if (curFilesMatched == maxFilesMatched) { matched.push_back((const ADGameDescription *)g); } } } // We didn't find a match if (matched.empty()) return NULL; return *matched.begin(); }
ADDetectedGame DirectorMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { // TODO: Handle Mac fallback // reset fallback description Director::DirectorGameDescription *desc = &s_fallbackDesc; desc->desc.gameId = "director"; desc->desc.extra = ""; desc->desc.language = Common::UNK_LANG; desc->desc.flags = ADGF_NO_FLAGS; desc->desc.platform = Common::kPlatformWindows; desc->desc.guiOptions = GUIO0(); desc->desc.filesDescriptions[0].fileName = 0; desc->version = 0; desc->gameID = Director::GID_GENERIC; for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { if (file->isDirectory()) continue; Common::String fileName = file->getName(); fileName.toLowercase(); if (!fileName.hasSuffix(".exe")) continue; Common::File f; if (!f.open(*file)) continue; f.seek(-4, SEEK_END); uint32 offset = f.readUint32LE(); if (f.eos() || offset == 0 || offset >= (uint32)(f.size() - 4)) continue; f.seek(offset); uint32 tag = f.readUint32LE(); switch (tag) { case MKTAG('3', '9', 'J', 'P'): desc->version = 4; break; case MKTAG('P', 'J', '9', '5'): desc->version = 5; break; case MKTAG('P', 'J', '0', '0'): desc->version = 7; break; default: // Prior to version 4, there was no tag here. So we'll use a bit of a // heuristic to detect. The first field is the entry count, of which // there should only be one. if ((tag & 0xFFFF) != 1) continue; f.skip(3); uint32 mmmSize = f.readUint32LE(); if (f.eos() || mmmSize == 0) continue; byte fileNameSize = f.readByte(); if (f.eos()) continue; f.skip(fileNameSize); byte directoryNameSize = f.readByte(); if (f.eos()) continue; f.skip(directoryNameSize); if (f.pos() != f.size() - 4) continue; // Assume v3 at this point (for now at least) desc->version = 3; } strncpy(s_fallbackFileNameBuffer, fileName.c_str(), 50); s_fallbackFileNameBuffer[50] = '\0'; desc->desc.filesDescriptions[0].fileName = s_fallbackFileNameBuffer; warning("Director fallback detection D%d", desc->version); return ADDetectedGame(&desc->desc); } return ADDetectedGame(); }