GameList AdvancedMetaEngine::detectGames(const Common::FSList &fslist) const { ADGameDescList matches = detectGame(fslist, params, Common::UNK_LANG, Common::kPlatformUnknown, ""); GameList detectedGames; if (cleanupPirated(matches)) return detectedGames; if (matches.empty()) { // Use fallback detector if there were no matches by other means const ADGameDescription *fallbackDesc = fallbackDetect(fslist); if (fallbackDesc != 0) { GameDescriptor desc(toGameDescriptor(*fallbackDesc, params.list)); updateGameDescriptor(desc, fallbackDesc, params); detectedGames.push_back(desc); } } else { // Otherwise use the found matches for (uint i = 0; i < matches.size(); i++) { GameDescriptor desc(toGameDescriptor(*matches[i], params.list)); updateGameDescriptor(desc, matches[i], params); detectedGames.push_back(desc); } } return detectedGames; }
GameList AdvancedMetaEngine::detectGames(const Common::FSList &fslist) const { ADGameDescList matches; GameList detectedGames; FileMap allFiles; if (fslist.empty()) return detectedGames; // Compose a hashmap of all files in fslist. composeFileHashMap(allFiles, fslist, (_maxScanDepth == 0 ? 1 : _maxScanDepth)); // Run the detector on this matches = detectGame(fslist.begin()->getParent(), allFiles, Common::UNK_LANG, Common::kPlatformUnknown, ""); if (matches.empty()) { // Use fallback detector if there were no matches by other means const ADGameDescription *fallbackDesc = fallbackDetect(allFiles, fslist); if (fallbackDesc != 0) { GameDescriptor desc(toGameDescriptor(*fallbackDesc, _gameIds)); updateGameDescriptor(desc, fallbackDesc); detectedGames.push_back(desc); } } else { // Otherwise use the found matches cleanupPirated(matches); for (uint i = 0; i < matches.size(); i++) { GameDescriptor desc(toGameDescriptor(*matches[i], _gameIds)); updateGameDescriptor(desc, matches[i]); detectedGames.push_back(desc); } } return detectedGames; }
bool cleanupPirated(ADGameDescList &matched) { // OKay, now let's sense presence of pirated games if (!matched.empty()) { for (uint j = 0; j < matched.size();) { if (matched[j]->flags & ADGF_PIRATED) matched.remove_at(j); else ++j; } // We ruled out all variants and now have nothing if (matched.empty()) { warning("Illegitimate game copy detected. We provide no support in such cases"); return true; } } return false; }
/** * 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 ADGameDescList detectGame(const Common::FSList &fslist, const ADParams ¶ms, Common::Language language, Common::Platform platform, const Common::String &extra) { FileMap allFiles; SizeMD5Map filesSizeMD5; const ADGameFileDescription *fileDesc; const ADGameDescription *g; const byte *descPtr; if (fslist.empty()) return ADGameDescList(); Common::FSNode parent = fslist.begin()->getParent(); debug(3, "Starting detection in dir '%s'", parent.getPath().c_str()); // First we compose a hashmap of all files in fslist. // Includes nifty stuff like removing trailing dots and ignoring case. composeFileHashMap(fslist, allFiles, (params.depth == 0 ? 1 : params.depth), params.directoryGlobs); // Check which files are included in some ADGameDescription *and* present // in fslist. Compute MD5s and file sizes for these files. for (descPtr = params.descs; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += params.descItemSize) { g = (const ADGameDescription *)descPtr; for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) { Common::String fname = fileDesc->fileName; SizeMD5 tmp; if (filesSizeMD5.contains(fname)) continue; // FIXME/TODO: We don't handle the case that a file is listed as a regular // file and as one with resource fork. if (g->flags & ADGF_MACRESFORK) { Common::MacResManager *macResMan = new Common::MacResManager(); if (macResMan->open(parent, fname)) { tmp.md5 = macResMan->computeResForkMD5AsString(params.md5Bytes); tmp.size = macResMan->getResForkDataSize(); debug(3, "> '%s': '%s'", fname.c_str(), tmp.md5.c_str()); filesSizeMD5[fname] = tmp; } delete macResMan; } else { if (allFiles.contains(fname)) { debug(3, "+ %s", fname.c_str()); Common::File testFile; if (testFile.open(allFiles[fname])) { tmp.size = (int32)testFile.size(); tmp.md5 = Common::computeStreamMD5AsString(testFile, params.md5Bytes); } else { tmp.size = -1; } debug(3, "> '%s': '%s'", fname.c_str(), tmp.md5.c_str()); filesSizeMD5[fname] = tmp; } } } } ADGameDescList matched; int maxFilesMatched = 0; bool gotAnyMatchesWithAllFiles = false; // MD5 based matching uint i; for (i = 0, descPtr = params.descs; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += params.descItemSize, ++i) { g = (const ADGameDescription *)descPtr; bool fileMissing = false; // Do not even bother to look at entries which do not have matching // language and platform (if specified). if ((language != Common::UNK_LANG && g->language != Common::UNK_LANG && g->language != language && !(language == Common::EN_ANY && (g->flags & ADGF_ADDENGLISH))) || (platform != Common::kPlatformUnknown && g->platform != Common::kPlatformUnknown && g->platform != platform)) { continue; } if ((params.flags & kADFlagUseExtraAsHint) && !extra.empty() && g->extra != extra) continue; bool allFilesPresent = true; int curFilesMatched = 0; // Try to match all files for this game for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) { Common::String tstr = fileDesc->fileName; if (!filesSizeMD5.contains(tstr)) { fileMissing = true; allFilesPresent = false; break; } if (fileDesc->md5 != NULL && fileDesc->md5 != filesSizeMD5[tstr].md5) { debug(3, "MD5 Mismatch. Skipping (%s) (%s)", fileDesc->md5, filesSizeMD5[tstr].md5.c_str()); fileMissing = true; break; } if (fileDesc->fileSize != -1 && fileDesc->fileSize != filesSizeMD5[tstr].size) { debug(3, "Size Mismatch. Skipping"); fileMissing = true; break; } debug(3, "Matched file: %s", tstr.c_str()); curFilesMatched++; } // We found at least one entry with all required files present. // That means that we got new variant of the game. // // Without this check we would have erroneous checksum display // where only located files will be enlisted. // // Potentially this could rule out variants where some particular file // is really missing, but the developers should better know about such // cases. if (allFilesPresent) gotAnyMatchesWithAllFiles = true; if (!fileMissing) { debug(2, "Found game: %s (%s %s/%s) (%d)", g->gameid, g->extra, getPlatformDescription(g->platform), getLanguageDescription(g->language), i); if (curFilesMatched > maxFilesMatched) { debug(2, " ... new best match, removing all previous candidates"); maxFilesMatched = curFilesMatched; matched.clear(); // Remove any prior, lower ranked matches. matched.push_back(g); } else if (curFilesMatched == maxFilesMatched) { matched.push_back(g); } else { debug(2, " ... skipped"); } } else { debug(5, "Skipping game: %s (%s %s/%s) (%d)", g->gameid, g->extra, getPlatformDescription(g->platform), getLanguageDescription(g->language), i); } } // We didn't find a match if (matched.empty()) { if (!filesSizeMD5.empty() && gotAnyMatchesWithAllFiles) { reportUnknown(parent, filesSizeMD5); } // Filename based fallback if (params.fileBasedFallback != 0) matched = detectGameFilebased(allFiles, params); } return matched; }
ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra) const { ADFilePropertiesMap filesProps; const ADGameFileDescription *fileDesc; const ADGameDescription *g; const byte *descPtr; debug(3, "Starting detection in dir '%s'", parent.getPath().c_str()); // Check which files are included in some ADGameDescription *and* are present. // Compute MD5s and file sizes for these files. for (descPtr = _gameDescriptors; ((const ADGameDescription *)descPtr)->gameId != 0; descPtr += _descItemSize) { g = (const ADGameDescription *)descPtr; for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) { Common::String fname = fileDesc->fileName; ADFileProperties tmp; if (filesProps.contains(fname)) continue; if (getFileProperties(parent, allFiles, *g, fname, tmp)) { debug(3, "> '%s': '%s'", fname.c_str(), tmp.md5.c_str()); filesProps[fname] = tmp; } } } ADGameDescList matched; ADGameIdList matchedGameIds; int maxFilesMatched = 0; bool gotAnyMatchesWithAllFiles = false; // MD5 based matching uint i; for (i = 0, descPtr = _gameDescriptors; ((const ADGameDescription *)descPtr)->gameId != 0; descPtr += _descItemSize, ++i) { g = (const ADGameDescription *)descPtr; bool fileMissing = false; // Do not even bother to look at entries which do not have matching // language and platform (if specified). if ((language != Common::UNK_LANG && g->language != Common::UNK_LANG && g->language != language && !(language == Common::EN_ANY && (g->flags & ADGF_ADDENGLISH))) || (platform != Common::kPlatformUnknown && g->platform != Common::kPlatformUnknown && g->platform != platform)) { continue; } if ((_flags & kADFlagUseExtraAsHint) && !extra.empty() && g->extra != extra) continue; bool allFilesPresent = true; int curFilesMatched = 0; bool hashOrSizeMismatch = false; // Try to match all files for this game for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) { Common::String tstr = fileDesc->fileName; if (!filesProps.contains(tstr)) { fileMissing = true; allFilesPresent = false; break; } if (hashOrSizeMismatch) continue; if (fileDesc->md5 != NULL && fileDesc->md5 != filesProps[tstr].md5) { debug(3, "MD5 Mismatch. Skipping (%s) (%s)", fileDesc->md5, filesProps[tstr].md5.c_str()); fileMissing = true; hashOrSizeMismatch = true; continue; } if (fileDesc->fileSize != -1 && fileDesc->fileSize != filesProps[tstr].size) { debug(3, "Size Mismatch. Skipping"); fileMissing = true; hashOrSizeMismatch = true; continue; } debug(3, "Matched file: %s", tstr.c_str()); curFilesMatched++; } // We found at least one entry with all required files present. // That means that we got new variant of the game. // // Without this check we would have erroneous checksum display // where only located files will be enlisted. // // Potentially this could rule out variants where some particular file // is really missing, but the developers should better know about such // cases. if (allFilesPresent) { gotAnyMatchesWithAllFiles = true; if (!matchedGameIds.size() || strcmp(matchedGameIds.back(), g->gameId) != 0) matchedGameIds.push_back(g->gameId); } if (!fileMissing) { debug(2, "Found game: %s (%s %s/%s) (%d)", g->gameId, g->extra, getPlatformDescription(g->platform), getLanguageDescription(g->language), i); if (curFilesMatched > maxFilesMatched) { debug(2, " ... new best match, removing all previous candidates"); maxFilesMatched = curFilesMatched; matched.clear(); // Remove any prior, lower ranked matches. matched.push_back(g); } else if (curFilesMatched == maxFilesMatched) { matched.push_back(g); } else { debug(2, " ... skipped"); } } else { debug(5, "Skipping game: %s (%s %s/%s) (%d)", g->gameId, g->extra, getPlatformDescription(g->platform), getLanguageDescription(g->language), i); } } // We didn't find a match if (matched.empty()) { if (!filesProps.empty() && gotAnyMatchesWithAllFiles) { reportUnknown(parent, filesProps, matchedGameIds); } // Filename based fallback } return matched; }