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); } } } }
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); } } } }
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; }
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); } } } }
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 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 } }
/** * 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(); }
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; }
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; }