/** * 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(); }
static void detectGames(const Common::FSList &fslist, Common::List<DetectorResult> &results, const char *gameid) { DescMap fileMD5Map; DetectorResult dr; // Dive one level down since mac indy3/loom has its files split into directories. See Bug #1438631 composeFileHashMap(fslist, fileMD5Map, 2, directoryGlobs); // Iterate over all filename patterns. for (const GameFilenamePattern *gfp = gameFilenamesTable; gfp->gameid; ++gfp) { // If a gameid was specified, we only try to detect that specific game, // so we can just skip over everything with a differing gameid. if (gameid && scumm_stricmp(gameid, gfp->gameid)) continue; // Generate the detectname corresponding to the gfp. If the file doesn't // exist in the directory we are looking at, we can skip to the next // one immediately. Common::String file(generateFilenameForDetection(gfp->pattern, gfp->genMethod)); if (!fileMD5Map.contains(file)) continue; // Reset the DetectorResult variable dr.fp.pattern = gfp->pattern; dr.fp.genMethod = gfp->genMethod; dr.game.gameid = 0; dr.language = gfp->language; dr.md5.clear(); dr.extra = 0; // ____ _ _ // | _ \ __ _ _ __| |_ / | // | |_) / _` | '__| __| | | // | __/ (_| | | | |_ | | // |_| \__,_|_| \__| |_| // // PART 1: Trying to find an exact match using MD5. // // // Background: We found a valid detection file. Check if its MD5 // checksum occurs in our MD5 table. If it does, try to use that // to find an exact match. // // We only do that if the MD5 hadn't already been computed (since // we may look at some detection files multiple times). // DetectorDesc &d = fileMD5Map[file]; if (d.md5.empty()) { Common::SeekableReadStream *tmp = 0; bool isDiskImg = (file.hasSuffix(".d64") || file.hasSuffix(".dsk") || file.hasSuffix(".prg")); if (isDiskImg) { tmp = openDiskImage(d.node, gfp); debug(2, "Falling back to disk-based detection"); } else { tmp = d.node.createReadStream(); } Common::String md5str; if (tmp) md5str = computeStreamMD5AsString(*tmp, kMD5FileSizeLimit); if (!md5str.empty()) { d.md5 = md5str; d.md5Entry = findInMD5Table(md5str.c_str()); dr.md5 = d.md5; if (d.md5Entry) { // Exact match found. Compute the precise game settings. computeGameSettingsFromMD5(fslist, gfp, d.md5Entry, dr); // Print some debug info int filesize = tmp->size(); if (d.md5Entry->filesize != filesize) debug(1, "SCUMM detector found matching file '%s' with MD5 %s, size %d\n", file.c_str(), md5str.c_str(), filesize); // Sanity check: We *should* have found a matching gameid / variant at this point. // If not, then there's a bug in our data tables... assert(dr.game.gameid != 0); // Add it to the list of detected games results.push_back(dr); } } if (isDiskImg) closeDiskImage((ScummDiskImage*)tmp); delete tmp; } // If an exact match for this file has already been found, don't bother // looking at it anymore. if (d.md5Entry) continue; // ____ _ ____ // | _ \ __ _ _ __| |_ |___ \ * // | |_) / _` | '__| __| __) | // | __/ (_| | | | |_ / __/ // |_| \__,_|_| \__| |_____| // // PART 2: Fuzzy matching for files with unknown MD5. // // We loop over the game variants matching the gameid associated to // the gfp record. We then try to decide for each whether it could be // appropriate or not. dr.md5 = d.md5; for (const GameSettings *g = gameVariantsTable; g->gameid; ++g) { // Skip over entries with a different gameid. if (g->gameid[0] == 0 || scumm_stricmp(gfp->gameid, g->gameid)) continue; dr.game = *g; dr.extra = g->variant; // FIXME: We (ab)use 'variant' for the 'extra' description for now. if (gfp->platform != Common::kPlatformUnknown) dr.game.platform = gfp->platform; // If a variant has been specified, use that! if (gfp->variant) { if (!scumm_stricmp(gfp->variant, g->variant)) { // perfect match found results.push_back(dr); break; } continue; } // HACK: Perhaps it is some modified translation? dr.language = detectLanguage(fslist, g->id); // Add the game/variant to the candidates list if it is consistent // with the file(s) we are seeing. if (testGame(g, fileMD5Map, file)) results.push_back(dr); } } }