diff options
31 files changed, 597 insertions, 428 deletions
diff --git a/base/commandLine.cpp b/base/commandLine.cpp index 83c7b56171..8e701408ef 100644 --- a/base/commandLine.cpp +++ b/base/commandLine.cpp @@ -864,12 +864,20 @@ static GameList getGameList(const Common::FSNode &dir) { } // detect Games - GameList candidates(EngineMan.detectGames(files)); - Common::String dataPath = dir.getPath(); - // add game data path - for (GameList::iterator v = candidates.begin(); v != candidates.end(); ++v) { - (*v)["path"] = dataPath; + DetectionResults detectionResults = EngineMan.detectGames(files); + + if (detectionResults.foundUnknownGames()) { + Common::String report = detectionResults.generateUnknownGameReport(false, 80); + g_system->logMessage(LogMessageType::kInfo, report.c_str()); } + + DetectedGames detectedGames = detectionResults.listRecognizedGames(); + + GameList candidates; + for (uint i = 0; i < detectedGames.size(); i++) { + candidates.push_back(detectedGames[i].matchedGame); + } + return candidates; } @@ -1014,7 +1022,14 @@ static void runDetectorTest() { continue; } - GameList candidates(EngineMan.detectGames(files)); + DetectionResults detectionResults = EngineMan.detectGames(files); + DetectedGames detectedGames = detectionResults.listRecognizedGames(); + + GameList candidates; + for (uint i = 0; i < detectedGames.size(); i++) { + candidates.push_back(detectedGames[i].matchedGame); + } + bool gameidDiffers = false; GameList::iterator x; for (x = candidates.begin(); x != candidates.end(); ++x) { @@ -1092,7 +1107,14 @@ void upgradeTargets() { Common::Platform plat = Common::parsePlatform(dom.getVal("platform")); Common::String desc(dom.getVal("description")); - GameList candidates(EngineMan.detectGames(files)); + DetectionResults detectionResults = EngineMan.detectGames(files); + DetectedGames detectedGames = detectionResults.listRecognizedGames(); + + GameList candidates; + for (uint i = 0; i < detectedGames.size(); i++) { + candidates.push_back(detectedGames[i].matchedGame); + } + GameDescriptor *g = 0; // We proceed as follows: @@ -1100,7 +1122,7 @@ void upgradeTargets() { // * If there is a unique detector match, trust it. // * If there are multiple match, run over them comparing gameid, language and platform. // If we end up with a unique match, use it. Otherwise, skip. - if (candidates.size() == 0) { + if (candidates.empty()) { printf(" ... failed to detect game, skipping\n"); continue; } diff --git a/base/plugins.cpp b/base/plugins.cpp index 852786919b..02f6998ad9 100644 --- a/base/plugins.cpp +++ b/base/plugins.cpp @@ -22,6 +22,7 @@ #include "base/plugins.h" +#include "common/translation.h" #include "common/func.h" #include "common/debug.h" #include "common/config-manager.h" @@ -514,8 +515,9 @@ GameDescriptor EngineManager::findGameInLoadedPlugins(const Common::String &game return result; } -GameList EngineManager::detectGames(const Common::FSList &fslist, bool useUnknownGameDialog) const { - GameList candidates; +DetectionResults EngineManager::detectGames(const Common::FSList &fslist) const { + DetectedGames candidates; + Common::String path = fslist.begin()->getParent().getPath(); PluginList plugins; PluginList::const_iterator iter; PluginManager::instance().loadFirstPlugin(); @@ -524,17 +526,25 @@ GameList EngineManager::detectGames(const Common::FSList &fslist, bool useUnknow // Iterate over all known games and for each check if it might be // the game in the presented directory. for (iter = plugins.begin(); iter != plugins.end(); ++iter) { - candidates.push_back((*iter)->get<MetaEngine>().detectGames(fslist, useUnknownGameDialog)); + const MetaEngine &metaEngine = (*iter)->get<MetaEngine>(); + DetectedGames engineCandidates = metaEngine.detectGames(fslist); + + for (uint i = 0; i < engineCandidates.size(); i++) { + engineCandidates[i].engineName = metaEngine.getName(); + engineCandidates[i].matchedGame["path"] = path; + candidates.push_back(engineCandidates[i]); + } + } } while (PluginManager::instance().loadNextPlugin()); - return candidates; + + return DetectionResults(candidates); } const PluginList &EngineManager::getPlugins() const { return PluginManager::instance().getPlugins(PLUGIN_TYPE_ENGINE); } - // Music plugins #include "audio/musicplugin.h" diff --git a/engines/adl/detection.cpp b/engines/adl/detection.cpp index 14646c78e3..2276576baa 100644 --- a/engines/adl/detection.cpp +++ b/engines/adl/detection.cpp @@ -332,9 +332,9 @@ public: int getMaximumSaveSlot() const { return 'O' - 'A'; } SaveStateList listSaves(const char *target) const; void removeSaveState(const char *target, int slot) const; - virtual ADGameDescList detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra, bool useUnknownGameDialog = false) const; + ADDetectedGames detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra) const override; - bool addFileProps(const FileMap &allFiles, Common::String fname, ADFilePropertiesMap &filePropsMap) const; + bool addFileProps(const FileMap &allFiles, Common::String fname, FilePropertiesMap &filePropsMap) const; bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const; }; @@ -492,14 +492,14 @@ Common::Platform getPlatform(const AdlGameDescription &adlDesc) { return adlDesc.desc.platform; } -bool AdlMetaEngine::addFileProps(const FileMap &allFiles, Common::String fname, ADFilePropertiesMap &filePropsMap) const { +bool AdlMetaEngine::addFileProps(const FileMap &allFiles, Common::String fname, FilePropertiesMap &filePropsMap) const { if (filePropsMap.contains(fname)) return true; if (!allFiles.contains(fname)) return false; - ADFileProperties fileProps; + FileProperties fileProps; fileProps.size = computeMD5(allFiles[fname], fileProps.md5, 16384); if (fileProps.size != -1) { @@ -511,42 +511,39 @@ bool AdlMetaEngine::addFileProps(const FileMap &allFiles, Common::String fname, } // Based on AdvancedMetaEngine::detectGame -ADGameDescList AdlMetaEngine::detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra, bool useUnknownGameDialog) const { +ADDetectedGames AdlMetaEngine::detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra) const { // We run the file-based detector first and then add to the returned list - ADGameDescList matched = AdvancedMetaEngine::detectGame(parent, allFiles, language, platform, extra, useUnknownGameDialog); + ADDetectedGames matched = AdvancedMetaEngine::detectGame(parent, allFiles, language, platform, extra); debug(3, "Starting disk image detection in dir '%s'", parent.getPath().c_str()); - ADFilePropertiesMap filesProps; - ADGameIdList matchedGameIds; + FilePropertiesMap filesProps; bool gotAnyMatchesWithAllFiles = false; for (uint g = 0; gameDiskDescriptions[g].desc.gameId != 0; ++g) { - const ADGameDescription &desc = gameDiskDescriptions[g].desc; + ADDetectedGame game(&gameDiskDescriptions[g].desc); // Skip games that don't meet the language/platform/extra criteria - if (language != Common::UNK_LANG && desc.language != Common::UNK_LANG) { - if (desc.language != language && !(language == Common::EN_ANY && (desc.flags & ADGF_ADDENGLISH))) - continue; + if (language != Common::UNK_LANG && game.desc->language != Common::UNK_LANG) { + if (game.desc->language != language && !(language == Common::EN_ANY && (game.desc->flags & ADGF_ADDENGLISH))) + continue; } - if (platform != Common::kPlatformUnknown && desc.platform != Common::kPlatformUnknown && desc.platform != platform) + if (platform != Common::kPlatformUnknown && game.desc->platform != Common::kPlatformUnknown && game.desc->platform != platform) continue; - if ((_flags & kADFlagUseExtraAsHint) && !extra.empty() && desc.extra != extra) + if ((_flags & kADFlagUseExtraAsHint) && !extra.empty() && game.desc->extra != extra) continue; - bool fileMissing = false; bool allFilesPresent = true; - bool hashOrSizeMismatch = false; - for (uint f = 0; desc.filesDescriptions[f].fileName; ++f) { - const ADGameFileDescription &fDesc = desc.filesDescriptions[f]; + for (uint f = 0; game.desc->filesDescriptions[f].fileName; ++f) { + const ADGameFileDescription &fDesc = game.desc->filesDescriptions[f]; Common::String fileName; bool foundDiskImage = false; for (uint e = 0; e < ARRAYSIZE(diskImageExts); ++e) { - if (diskImageExts[e].platform == desc.platform) { + if (diskImageExts[e].platform == game.desc->platform) { Common::String testFileName(fDesc.fileName); testFileName += diskImageExts[e].extension; @@ -563,49 +560,41 @@ ADGameDescList AdlMetaEngine::detectGame(const Common::FSNode &parent, const Fil } if (!foundDiskImage) { - fileMissing = true; allFilesPresent = false; break; } - if (hashOrSizeMismatch) + game.matchedFiles[fileName] = filesProps[fileName]; + + if (game.hasUnknownFiles) continue; if (fDesc.md5 && fDesc.md5 != filesProps[fileName].md5) { debug(3, "MD5 Mismatch. Skipping (%s) (%s)", fDesc.md5, filesProps[fileName].md5.c_str()); - fileMissing = true; - hashOrSizeMismatch = true; + game.hasUnknownFiles = true; continue; } if (fDesc.fileSize != -1 && fDesc.fileSize != filesProps[fileName].size) { debug(3, "Size Mismatch. Skipping"); - fileMissing = true; - hashOrSizeMismatch = true; + game.hasUnknownFiles = true; continue; } debug(3, "Matched file: %s", fileName.c_str()); } - if (!fileMissing) { - debug(2, "Found game: %s (%s/%s) (%d)", desc.gameId, getPlatformDescription(desc.platform), getLanguageDescription(desc.language), g); - matched.push_back(&desc); + if (allFilesPresent && !game.hasUnknownFiles) { + debug(2, "Found game: %s (%s/%s) (%d)", game.desc->gameId, getPlatformDescription(game.desc->platform), getLanguageDescription(game.desc->language), g); + gotAnyMatchesWithAllFiles = true; + matched.push_back(game); } else { - if (allFilesPresent) { - gotAnyMatchesWithAllFiles = true; - if (!matchedGameIds.size() || strcmp(matchedGameIds.back(), desc.gameId) != 0) - matchedGameIds.push_back(desc.gameId); + if (allFilesPresent && !gotAnyMatchesWithAllFiles) { + if (matched.empty() || strcmp(matched.back().desc->gameId, game.desc->gameId) != 0) + matched.push_back(game); } - debug(5, "Skipping game: %s (%s/%s) (%d)", desc.gameId, getPlatformDescription(desc.platform), getLanguageDescription(desc.language), g); - } - } - - // TODO: This could be improved to handle matched and unknown games together in a single directory - if (matched.empty()) { - if (!filesProps.empty() && gotAnyMatchesWithAllFiles) { - reportUnknown(parent, filesProps, matchedGameIds, useUnknownGameDialog); + debug(5, "Skipping game: %s (%s/%s) (%d)", game.desc->gameId, getPlatformDescription(game.desc->platform), getLanguageDescription(game.desc->language), g); } } diff --git a/engines/advancedDetector.cpp b/engines/advancedDetector.cpp index dea9b8e6ec..9c1a700423 100644 --- a/engines/advancedDetector.cpp +++ b/engines/advancedDetector.cpp @@ -30,8 +30,6 @@ #include "common/textconsole.h" #include "common/translation.h" #include "gui/EventRecorder.h" -#include "gui/gui-manager.h" -#include "engines/unknown-game-dialog.h" #include "engines/advancedDetector.h" #include "engines/obsolete.h" @@ -63,6 +61,17 @@ static GameDescriptor toGameDescriptor(const ADGameDescription &g, const PlainGa return gd; } +DetectedGame AdvancedMetaEngine::toDetectedGame(const ADDetectedGame &adGame) const { + DetectedGame game; + game.engineName = getName(); + game.gameId = adGame.desc->gameId; + game.hasUnknownFiles = adGame.hasUnknownFiles; + game.matchedFiles = adGame.matchedFiles; + game.matchedGame = toGameDescriptor(*adGame.desc, _gameIds); + updateGameDescriptor(game.matchedGame, adGame.desc); + return game; +} + /** * Generate a preferred target value as * GAMEID-PLAFORM-LANG @@ -129,11 +138,11 @@ void AdvancedMetaEngine::updateGameDescriptor(GameDescriptor &desc, const ADGame desc.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::EN_ANY)); } -bool cleanupPirated(ADGameDescList &matched) { +bool cleanupPirated(ADDetectedGames &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) + if (matched[j].desc->flags & ADGF_PIRATED) matched.remove_at(j); else ++j; @@ -150,35 +159,46 @@ bool cleanupPirated(ADGameDescList &matched) { } -GameList AdvancedMetaEngine::detectGames(const Common::FSList &fslist, bool useUnknownGameDialog) const { - ADGameDescList matches; - GameList detectedGames; +DetectedGames AdvancedMetaEngine::detectGames(const Common::FSList &fslist) const { FileMap allFiles; if (fslist.empty()) - return detectedGames; + 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, "", useUnknownGameDialog); + ADDetectedGames 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 != nullptr) { - GameDescriptor desc(toGameDescriptor(*fallbackDesc, _gameIds)); - updateGameDescriptor(desc, fallbackDesc); - detectedGames.push_back(desc); + cleanupPirated(matches); + + DetectedGames detectedGames; + for (uint i = 0; i < matches.size(); i++) { + DetectedGame game = toDetectedGame(matches[i]); + + if (game.hasUnknownFiles) { + // Non fallback games with unknown files cannot be added/launched + game.canBeAdded = false; } - } 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); + + detectedGames.push_back(game); + } + + bool foundKnownGames = false; + for (uint i = 0; i < detectedGames.size(); i++) { + foundKnownGames |= detectedGames[i].canBeAdded; + } + + if (!foundKnownGames) { + // Use fallback detector if there were no matches by other means + ADDetectedGame fallbackDetectionResult = fallbackDetect(allFiles, fslist); + + if (fallbackDetectionResult.desc) { + DetectedGame fallbackDetectedGame = toDetectedGame(fallbackDetectionResult); + fallbackDetectedGame.preferredTarget += "-fallback"; + + detectedGames.push_back(fallbackDetectedGame); } } @@ -216,7 +236,6 @@ const ExtraGuiOptions AdvancedMetaEngine::getExtraGuiOptions(const Common::Strin Common::Error AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) const { assert(engine); - const ADGameDescription *agdDesc = 0; Common::Language language = Common::UNK_LANG; Common::Platform platform = Common::kPlatformUnknown; Common::String extra; @@ -266,46 +285,43 @@ Common::Error AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) composeFileHashMap(allFiles, files, (_maxScanDepth == 0 ? 1 : _maxScanDepth)); // Run the detector on this - ADGameDescList matches = detectGame(files.begin()->getParent(), allFiles, language, platform, extra); + ADDetectedGames matches = detectGame(files.begin()->getParent(), allFiles, language, platform, extra); if (cleanupPirated(matches)) return Common::kNoGameDataFoundError; - if (_singleId == NULL) { - // Find the first match with correct gameid. - for (uint i = 0; i < matches.size(); i++) { - if (matches[i]->gameId == gameid) { - agdDesc = matches[i]; - break; - } + ADDetectedGame agdDesc; + for (uint i = 0; i < matches.size(); i++) { + if ((_singleId || matches[i].desc->gameId == gameid) && !matches[i].hasUnknownFiles) { + agdDesc = matches[i]; + break; } - } else if (matches.size() > 0) { - agdDesc = matches[0]; } - if (agdDesc == 0) { + if (!agdDesc.desc) { // Use fallback detector if there were no matches by other means - agdDesc = fallbackDetect(allFiles, files); - if (agdDesc != 0) { + ADDetectedGame fallbackDetectedGame = fallbackDetect(allFiles, files); + agdDesc = fallbackDetectedGame; + if (agdDesc.desc) { // Seems we found a fallback match. But first perform a basic // sanity check: the gameid must match. - if (_singleId == NULL && agdDesc->gameId != gameid) - agdDesc = 0; + if (!_singleId && agdDesc.desc->gameId != gameid) + agdDesc = ADDetectedGame(); } } - if (agdDesc == 0) + if (!agdDesc.desc) return Common::kNoGameDataFoundError; // If the GUI options were updated, we catch this here and update them in the users config // file transparently. - Common::String lang = getGameGUIOptionsDescriptionLanguage(agdDesc->language); - if (agdDesc->flags & ADGF_ADDENGLISH) + Common::String lang = getGameGUIOptionsDescriptionLanguage(agdDesc.desc->language); + if (agdDesc.desc->flags & ADGF_ADDENGLISH) lang += " " + getGameGUIOptionsDescriptionLanguage(Common::EN_ANY); - Common::updateGameGUIOptions(agdDesc->guiOptions + _guiOptions, lang); + Common::updateGameGUIOptions(agdDesc.desc->guiOptions + _guiOptions, lang); - GameDescriptor gameDescriptor = toGameDescriptor(*agdDesc, _gameIds); + GameDescriptor gameDescriptor = toGameDescriptor(*agdDesc.desc, _gameIds); bool showTestingWarning = false; @@ -320,65 +336,13 @@ Common::Error AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) return Common::kUserCanceled; debug(2, "Running %s", gameDescriptor.description().c_str()); - initSubSystems(agdDesc); - if (!createInstance(syst, engine, agdDesc)) + initSubSystems(agdDesc.desc); + if (!createInstance(syst, engine, agdDesc.desc)) return Common::kNoGameDataFoundError; else return Common::kNoError; } -void AdvancedMetaEngine::reportUnknown(const Common::FSNode &path, const ADFilePropertiesMap &filesProps, const ADGameIdList &matchedGameIds, bool useUnknownGameDialog) const { - const char *reportCommon = _s("The game in '%s' seems to be an unknown %s engine game " - "variant.\n\nPlease report the following data to the ScummVM " - "team at %s along with the name of the game you tried to add and " - "its version, language, etc.:"); - Common::String report = Common::String::format(reportCommon, path.getPath().c_str(), getName(), "https://bugs.scummvm.org/"); - Common::String reportTranslated = Common::String::format(_(reportCommon), path.getPath().c_str(), getName(), "https://bugs.scummvm.org/"); - Common::String bugtrackerAffectedEngine = getName(); - - if (!matchedGameIds.empty()) { - report += "\n\n"; - reportTranslated += "\n\n"; - report += "Matched game IDs:"; - reportTranslated += _("Matched game IDs:"); - report += " "; - reportTranslated += " "; - - for (ADGameIdList::const_iterator gameId = matchedGameIds.begin(); gameId != matchedGameIds.end(); ++gameId) { - if (gameId != matchedGameIds.begin()) { - report += ", "; - reportTranslated += ", "; - } - report += *gameId; - reportTranslated += *gameId; - } - } - - report += "\n\n"; - reportTranslated += "\n\n"; - - reportTranslated.wordWrap(65); - Common::String reportLog = report; - reportLog.wordWrap(80); - - Common::String unknownFiles; - for (ADFilePropertiesMap::const_iterator file = filesProps.begin(); file != filesProps.end(); ++file) - unknownFiles += Common::String::format(" {\"%s\", 0, \"%s\", %d},\n", file->_key.c_str(), file->_value.md5.c_str(), file->_value.size); - - report += unknownFiles; - reportTranslated += unknownFiles; - reportLog += unknownFiles + "\n"; - - // Write the original message about the unknown game to the log file - g_system->logMessage(LogMessageType::kInfo, reportLog.c_str()); - - // Check if the GUI is running, show the UnknownGameDialog and print the translated unknown game information - if (GUI::GuiManager::hasInstance() && g_gui.isActive() && useUnknownGameDialog == true) { - UnknownGameDialog dialog(report, reportTranslated, bugtrackerAffectedEngine); - dialog.runModal(); - } -} - void AdvancedMetaEngine::composeFileHashMap(FileMap &allFiles, const Common::FSList &fslist, int depth, const Common::String &parentName) const { if (depth <= 0) return; @@ -419,7 +383,7 @@ void AdvancedMetaEngine::composeFileHashMap(FileMap &allFiles, const Common::FSL } } -bool AdvancedMetaEngine::getFileProperties(const Common::FSNode &parent, const FileMap &allFiles, const ADGameDescription &game, const Common::String fname, ADFileProperties &fileProps) const { +bool AdvancedMetaEngine::getFileProperties(const Common::FSNode &parent, const FileMap &allFiles, const ADGameDescription &game, const Common::String fname, FileProperties &fileProps) const { // FIXME/TODO: We don't handle the case that a file is listed as a regular // file and as one with resource fork. @@ -449,8 +413,9 @@ bool AdvancedMetaEngine::getFileProperties(const Common::FSNode &parent, const F return true; } -ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra, bool useUnknownGameDialog) const { - ADFilePropertiesMap filesProps; +ADDetectedGames AdvancedMetaEngine::detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra) const { + FilePropertiesMap filesProps; + ADDetectedGames matched; const ADGameFileDescription *fileDesc; const ADGameDescription *g; @@ -465,7 +430,7 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) { Common::String fname = fileDesc->fileName; - ADFileProperties tmp; + FileProperties tmp; if (filesProps.contains(fname)) continue; @@ -477,8 +442,6 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons } } - ADGameDescList matched; - ADGameIdList matchedGameIds; int maxFilesMatched = 0; bool gotAnyMatchesWithAllFiles = false; @@ -486,7 +449,6 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons uint i; for (i = 0, descPtr = _gameDescriptors; ((const ADGameDescription *)descPtr)->gameId != nullptr; 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). @@ -499,34 +461,33 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons if ((_flags & kADFlagUseExtraAsHint) && !extra.empty() && g->extra != extra) continue; + ADDetectedGame game(g); bool allFilesPresent = true; int curFilesMatched = 0; - bool hashOrSizeMismatch = false; // Try to match all files for this game - for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) { + for (fileDesc = game.desc->filesDescriptions; fileDesc->fileName; fileDesc++) { Common::String tstr = fileDesc->fileName; if (!filesProps.contains(tstr)) { - fileMissing = true; allFilesPresent = false; break; } - if (hashOrSizeMismatch) + game.matchedFiles[tstr] = filesProps[tstr]; + + if (game.hasUnknownFiles) continue; if (fileDesc->md5 != nullptr && fileDesc->md5 != filesProps[tstr].md5) { debug(3, "MD5 Mismatch. Skipping (%s) (%s)", fileDesc->md5, filesProps[tstr].md5.c_str()); - fileMissing = true; - hashOrSizeMismatch = true; + game.hasUnknownFiles = true; continue; } if (fileDesc->fileSize != -1 && fileDesc->fileSize != filesProps[tstr].size) { debug(3, "Size Mismatch. Skipping"); - fileMissing = true; - hashOrSizeMismatch = true; + game.hasUnknownFiles = true; continue; } @@ -543,13 +504,12 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons // 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.empty() || strcmp(matchedGameIds.back(), g->gameId) != 0) - matchedGameIds.push_back(g->gameId); + if (allFilesPresent && !gotAnyMatchesWithAllFiles) { + if (matched.empty() || strcmp(matched.back().desc->gameId, g->gameId) != 0) + matched.push_back(game); } - if (!fileMissing) { + if (allFilesPresent && !game.hasUnknownFiles) { debug(2, "Found game: %s (%s %s/%s) (%d)", g->gameId, g->extra, getPlatformDescription(g->platform), getLanguageDescription(g->language), i); @@ -558,37 +518,29 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons maxFilesMatched = curFilesMatched; matched.clear(); // Remove any prior, lower ranked matches. - matched.push_back(g); + matched.push_back(game); } else if (curFilesMatched == maxFilesMatched) { - matched.push_back(g); + matched.push_back(game); } else { debug(2, " ... skipped"); } + gotAnyMatchesWithAllFiles = true; } 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, useUnknownGameDialog); - } - - // Filename based fallback - } - return matched; } -const ADGameDescription *AdvancedMetaEngine::detectGameFilebased(const FileMap &allFiles, const Common::FSList &fslist, const ADFileBasedFallback *fileBasedFallback, ADFilePropertiesMap *filesProps) const { +ADDetectedGame AdvancedMetaEngine::detectGameFilebased(const FileMap &allFiles, const Common::FSList &fslist, const ADFileBasedFallback *fileBasedFallback) const { const ADFileBasedFallback *ptr; const char* const* filenames; int maxNumMatchedFiles = 0; - const ADGameDescription *matchedDesc = 0; + ADDetectedGame result; for (ptr = fileBasedFallback; ptr->desc; ++ptr) { const ADGameDescription *agdesc = ptr->desc; @@ -609,25 +561,26 @@ const ADGameDescription *AdvancedMetaEngine::detectGameFilebased(const FileMap & debug(4, "Matched: %s", agdesc->gameId); if (numMatchedFiles > maxNumMatchedFiles) { - matchedDesc = agdesc; maxNumMatchedFiles = numMatchedFiles; debug(4, "and overridden"); - if (filesProps) { - for (filenames = ptr->filenames; *filenames; ++filenames) { - ADFileProperties tmp; + ADDetectedGame game(agdesc); + game.hasUnknownFiles = true; + + for (filenames = ptr->filenames; *filenames; ++filenames) { + FileProperties tmp; - if (getFileProperties(fslist.begin()->getParent(), allFiles, *agdesc, *filenames, tmp)) - (*filesProps)[*filenames] = tmp; - } + if (getFileProperties(fslist.begin()->getParent(), allFiles, *agdesc, *filenames, tmp)) + game.matchedFiles[*filenames] = tmp; } + result = game; } } } - return matchedDesc; + return result; } GameList AdvancedMetaEngine::getSupportedGames() const { diff --git a/engines/advancedDetector.h b/engines/advancedDetector.h index d7e85f86fe..f1f55d0b4b 100644 --- a/engines/advancedDetector.h +++ b/engines/advancedDetector.h @@ -48,20 +48,6 @@ struct ADGameFileDescription { }; /** - * A record describing the properties of a file. Used on the existing - * files while detecting a game. - */ -struct ADFileProperties { - int32 size; - Common::String md5; -}; - -/** - * A map of all relevant existing files in a game directory while detecting. - */ -typedef Common::HashMap<Common::String, ADFileProperties, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> ADFilePropertiesMap; - -/** * A shortcut to produce an empty ADGameFileDescription record. Used to mark * the end of a list of these. */ @@ -112,14 +98,19 @@ struct ADGameDescription { }; /** - * A list of pointers to ADGameDescription structs (or subclasses thereof). + * A game installation matching an AD game description */ -typedef Common::Array<const ADGameDescription *> ADGameDescList; +struct ADDetectedGame { + bool hasUnknownFiles; + FilePropertiesMap matchedFiles; + const ADGameDescription *desc; -/** - * A list of raw game ID strings. - */ -typedef Common::Array<const char *> ADGameIdList; + ADDetectedGame() : desc(nullptr), hasUnknownFiles(false) {} + explicit ADDetectedGame(const ADGameDescription *d) : desc(d), hasUnknownFiles(false) {} +}; + +/** A list of games detected by the AD */ +typedef Common::Array<ADDetectedGame> ADDetectedGames; /** * End marker for a table of ADGameDescription structs. Use this to @@ -278,7 +269,7 @@ public: virtual GameDescriptor findGame(const char *gameId) const; - virtual GameList detectGames(const Common::FSList &fslist, bool useUnknownGameDialog = false) const; + DetectedGames detectGames(const Common::FSList &fslist) const override; virtual Common::Error createInstance(OSystem *syst, Engine **engine) const; @@ -294,8 +285,8 @@ protected: * An (optional) generic fallback detect function which is invoked * if the regular MD5 based detection failed to detect anything. */ - virtual const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { - return 0; + virtual ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { + return ADDetectedGame(); } private: @@ -313,7 +304,7 @@ protected: * @param extra restrict results to specified extra string (only if kADFlagUseExtraAsHint is set) * @return list of ADGameDescription pointers corresponding to matched games */ - virtual ADGameDescList detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra, bool useUnknownGameDialog = false) const; + virtual ADDetectedGames detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra) const; /** * Iterates over all ADFileBasedFallback records inside fileBasedFallback. @@ -327,13 +318,7 @@ protected: * @param fileBasedFallback a list of ADFileBasedFallback records, zero-terminated * @param filesProps if not 0, return a map of properties for all detected files here */ - const ADGameDescription *detectGameFilebased(const FileMap &allFiles, const Common::FSList &fslist, const ADFileBasedFallback *fileBasedFallback, ADFilePropertiesMap *filesProps = 0) const; - - /** - * Log and print a report that we found an unknown game variant, together with the file - * names, sizes and MD5 sums. - */ - void reportUnknown(const Common::FSNode &path, const ADFilePropertiesMap &filesProps, const ADGameIdList &matchedGameIds = ADGameIdList(), bool useUnknownGameDialog = false) const; + ADDetectedGame detectGameFilebased(const FileMap &allFiles, const Common::FSList &fslist, const ADFileBasedFallback *fileBasedFallback) const; // TODO void updateGameDescriptor(GameDescriptor &desc, const ADGameDescription *realDesc) const; @@ -345,7 +330,10 @@ protected: void composeFileHashMap(FileMap &allFiles, const Common::FSList &fslist, int depth, const Common::String &parentName = Common::String()) const; /** Get the properties (size and MD5) of this file. */ - bool getFileProperties(const Common::FSNode &parent, const FileMap &allFiles, const ADGameDescription &game, const Common::String fname, ADFileProperties &fileProps) const; + bool getFileProperties(const Common::FSNode &parent, const FileMap &allFiles, const ADGameDescription &game, const Common::String fname, FileProperties &fileProps) const; + + /** Convert an AD game description into the shared game description format */ + DetectedGame toDetectedGame(const ADDetectedGame &adGame) const; }; #endif diff --git a/engines/agi/detection.cpp b/engines/agi/detection.cpp index 817be08f2c..39275c4f70 100644 --- a/engines/agi/detection.cpp +++ b/engines/agi/detection.cpp @@ -220,7 +220,7 @@ public: virtual void removeSaveState(const char *target, int slot) const; SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; - const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const; + ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const override; }; bool AgiMetaEngine::hasFeature(MetaEngineFeature f) const { @@ -421,7 +421,7 @@ SaveStateDescriptor AgiMetaEngine::querySaveMetaInfos(const char *target, int sl } } -const ADGameDescription *AgiMetaEngine::fallbackDetect(const FileMap &allFilesXXX, const Common::FSList &fslist) const { +ADDetectedGame AgiMetaEngine::fallbackDetect(const FileMap &allFilesXXX, const Common::FSList &fslist) const { typedef Common::HashMap<Common::String, int32> IntMap; IntMap allFiles; bool matchedUsingFilenames = false; @@ -584,10 +584,10 @@ const ADGameDescription *AgiMetaEngine::fallbackDetect(const FileMap &allFilesXX g_system->logMessage(LogMessageType::kWarning, fallbackWarning.c_str()); - return (const ADGameDescription *)&g_fallbackDesc; + return ADDetectedGame(&g_fallbackDesc.desc); } - return 0; + return ADDetectedGame(); } #if PLUGIN_ENABLED_DYNAMIC(AGI) diff --git a/engines/cge/detection.cpp b/engines/cge/detection.cpp index 4c23939d60..f6399d484c 100644 --- a/engines/cge/detection.cpp +++ b/engines/cge/detection.cpp @@ -126,7 +126,7 @@ public: return "Soltys (C) 1994-1996 L.K. Avalon"; } - virtual const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const; + ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const override; virtual bool hasFeature(MetaEngineFeature f) const; virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; virtual int getMaximumSaveSlot() const; @@ -135,13 +135,8 @@ public: virtual void removeSaveState(const char *target, int slot) const; }; -static const ADFileBasedFallback fileBasedFallback[] = { - { &gameDescriptions[0], { "vol.cat", "vol.dat", 0 } }, - { 0, { 0 } } -}; - static ADGameDescription s_fallbackDesc = { - "Soltys", + "soltys", "Unknown version", AD_ENTRY1(0, 0), // This should always be AD_ENTRY1(0, 0) in the fallback descriptor Common::UNK_LANG, @@ -150,28 +145,29 @@ static ADGameDescription s_fallbackDesc = { GUIO1(GAMEOPTION_COLOR_BLIND_DEFAULT_OFF) }; -const ADGameDescription *CGEMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { - ADFilePropertiesMap filesProps; +static const ADFileBasedFallback fileBasedFallback[] = { + { &s_fallbackDesc, { "vol.cat", "vol.dat", 0 } }, + { 0, { 0 } } +}; - const ADGameDescription *game; - game = detectGameFilebased(allFiles, fslist, CGE::fileBasedFallback, &filesProps); +ADDetectedGame CGEMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { + ADDetectedGame game = detectGameFilebased(allFiles, fslist, CGE::fileBasedFallback); - if (!game) - return nullptr; + if (!game.desc) + return ADDetectedGame(); SearchMan.addDirectory("CGEMetaEngine::fallbackDetect", fslist.begin()->getParent()); ResourceManager *resman; resman = new ResourceManager(); - bool result = resman->exist("CGE.SAY"); + bool sayFileFound = resman->exist("CGE.SAY"); delete resman; SearchMan.remove("CGEMetaEngine::fallbackDetect"); - if (!result) - return nullptr; + if (!sayFileFound) + return ADDetectedGame(); - reportUnknown(fslist.begin()->getParent(), filesProps); - return &s_fallbackDesc; + return game; } bool CGEMetaEngine::hasFeature(MetaEngineFeature f) const { diff --git a/engines/cge2/detection.cpp b/engines/cge2/detection.cpp index 648dcc01e2..ec6925ac74 100644 --- a/engines/cge2/detection.cpp +++ b/engines/cge2/detection.cpp @@ -132,7 +132,7 @@ public: return "Sfinx (C) 1994-1997 Janus B. Wisniewski and L.K. Avalon"; } - virtual const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const; + ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const override; virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; virtual bool hasFeature(MetaEngineFeature f) const; virtual int getMaximumSaveSlot() const; @@ -141,13 +141,8 @@ public: virtual void removeSaveState(const char *target, int slot) const; }; -static const ADFileBasedFallback fileBasedFallback[] = { - { &gameDescriptions[0], { "vol.cat", "vol.dat", 0 } }, - { 0, { 0 } } -}; - static ADGameDescription s_fallbackDesc = { - "Sfinx", + "sfinx", "Unknown version", AD_ENTRY1(0, 0), // This should always be AD_ENTRY1(0, 0) in the fallback descriptor Common::UNK_LANG, @@ -156,30 +151,31 @@ static ADGameDescription s_fallbackDesc = { GUIO1(GAMEOPTION_COLOR_BLIND_DEFAULT_OFF) }; +static const ADFileBasedFallback fileBasedFallback[] = { + { &s_fallbackDesc, { "vol.cat", "vol.dat", 0 } }, + { 0, { 0 } } +}; + // This fallback detection looks identical to the one used for CGE. In fact, the difference resides // in the ResourceManager which handles a different archive format. The rest of the detection is identical. -const ADGameDescription *CGE2MetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { - ADFilePropertiesMap filesProps; - - const ADGameDescription *game; - game = detectGameFilebased(allFiles, fslist, CGE2::fileBasedFallback, &filesProps); +ADDetectedGame CGE2MetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { + ADDetectedGame game = detectGameFilebased(allFiles, fslist, CGE2::fileBasedFallback); - if (!game) - return 0; + if (!game.desc) + return ADDetectedGame(); SearchMan.addDirectory("CGE2MetaEngine::fallbackDetect", fslist.begin()->getParent()); ResourceManager *resman; resman = new ResourceManager(); - bool result = resman->exist("CGE.SAY"); + bool sayFileFound = resman->exist("CGE.SAY"); delete resman; SearchMan.remove("CGE2MetaEngine::fallbackDetect"); - if (!result) - return 0; + if (!sayFileFound) + return ADDetectedGame(); - reportUnknown(fslist.begin()->getParent(), filesProps); - return &s_fallbackDesc; + return game; } bool CGE2MetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { diff --git a/engines/director/detection.cpp b/engines/director/detection.cpp index 16d838fcca..9d293846bc 100644 --- a/engines/director/detection.cpp +++ b/engines/director/detection.cpp @@ -112,7 +112,7 @@ public: return "Macromedia Director (C) Macromedia"; } - const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const; + ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const override; virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; }; @@ -141,7 +141,7 @@ static Director::DirectorGameDescription s_fallbackDesc = { static char s_fallbackFileNameBuffer[51]; -const ADGameDescription *DirectorMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { +ADDetectedGame DirectorMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { // TODO: Handle Mac fallback // reset fallback description @@ -230,10 +230,10 @@ const ADGameDescription *DirectorMetaEngine::fallbackDetect(const FileMap &allFi warning("Director fallback detection D%d", desc->version); - return (ADGameDescription *)desc; + return ADDetectedGame(&desc->desc); } - return 0; + return ADDetectedGame(); } #if PLUGIN_ENABLED_DYNAMIC(DIRECTOR) diff --git a/engines/game.cpp b/engines/game.cpp index 7ff51a99cc..177880c39d 100644 --- a/engines/game.cpp +++ b/engines/game.cpp @@ -22,6 +22,7 @@ #include "engines/game.h" #include "common/gui_options.h" +#include "common/translation.h" const PlainGameDescriptor *findPlainGameDescriptor(const char *gameid, const PlainGameDescriptor *list) { @@ -124,3 +125,89 @@ void GameDescriptor::setSupportLevel(GameSupportLevel gsl) { erase("gsl"); } } + +DetectionResults::DetectionResults(const DetectedGames &detectedGames) : + _detectedGames(detectedGames) { +} + +bool DetectionResults::foundUnknownGames() const { + for (uint i = 0; i < _detectedGames.size(); i++) { + if (_detectedGames[i].hasUnknownFiles) { + return true; + } + } + return false; +} + +DetectedGames DetectionResults::listRecognizedGames() { + DetectedGames candidates; + for (uint i = 0; i < _detectedGames.size(); i++) { + if (_detectedGames[i].canBeAdded) { + candidates.push_back(_detectedGames[i]); + } + } + return candidates; +} + +Common::String DetectionResults::generateUnknownGameReport(bool translate, uint32 wordwrapAt) const { + assert(!_detectedGames.empty()); + + const char *reportStart = _s("The game in '%s' seems to be an unknown game variant.\n\n" + "Please report the following data to the ScummVM team at %s " + "along with the name of the game you tried to add and " + "its version, language, etc.:"); + const char *reportEngineHeader = _s("Matched game IDs for the %s engine:"); + + Common::String report = Common::String::format( + translate ? _(reportStart) : reportStart, _detectedGames[0].matchedGame["path"].c_str(), + "https://bugs.scummvm.org/" + ); + report += "\n"; + + FilePropertiesMap matchedFiles; + + const char *currentEngineName = nullptr; + for (uint i = 0; i < _detectedGames.size(); i++) { + const DetectedGame &game = _detectedGames[i]; + + if (!game.hasUnknownFiles) continue; + + if (!currentEngineName || strcmp(currentEngineName, game.engineName) != 0) { + currentEngineName = game.engineName; + + // If the engine is not the same as for the previous entry, print an engine line header + report += "\n"; + report += Common::String::format( + translate ? _(reportEngineHeader) : reportEngineHeader, + game.engineName + ); + report += " "; + + } else { + report += ", "; + } + + // Add the gameId to the list of matched games for the engine + // TODO: Use the gameId here instead of the preferred target. + // This is currently impossible due to the AD singleId feature losing the information. + report += game.matchedGame["preferredtarget"]; + + // Consolidate matched files across all engines and detection entries + for (FilePropertiesMap::const_iterator it = game.matchedFiles.begin(); it != game.matchedFiles.end(); it++) { + matchedFiles.setVal(it->_key, it->_value); + } + } + + if (wordwrapAt) { + report.wordWrap(wordwrapAt); + } + + report += "\n\n"; + + for (FilePropertiesMap::const_iterator file = matchedFiles.begin(); file != matchedFiles.end(); ++file) + report += Common::String::format(" {\"%s\", 0, \"%s\", %d},\n", file->_key.c_str(), file->_value.md5.c_str(), file->_value.size); + + report += "\n"; + + return report; +} diff --git a/engines/game.h b/engines/game.h index e01e5c6885..54c1af3949 100644 --- a/engines/game.h +++ b/engines/game.h @@ -115,4 +115,108 @@ public: } }; +/** + * A record describing the properties of a file. Used on the existing + * files while detecting a game. + */ +struct FileProperties { + int32 size; + Common::String md5; + + FileProperties() : size(-1) {} +}; + +/** + * A map of all relevant existing files while detecting. + */ +typedef Common::HashMap<Common::String, FileProperties, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> FilePropertiesMap; + +struct DetectedGame { + /** + * The name of the engine supporting the detected game + */ + const char *engineName; + + /** + * The identifier of the detected game + * + * For engines using the singleId feature, this is the true engine-specific gameId, not the singleId. + */ + const char *gameId; + + /** + * A game was detected, but some files were not recognized + * + * This can happen when the md5 or size of the detected files did not match the engine's detection tables. + * When this is true, the list of matched files below contains detail about the unknown files. + * + * @see matchedFiles + */ + bool hasUnknownFiles; + + /** + * An optional list of the files that were used to match the game with the engine's detection tables + */ + FilePropertiesMap matchedFiles; + + /** + * This detection entry contains enough data to add the game to the configuration manager and launch it + * + * @see matchedGame + */ + bool canBeAdded; + + /** + * Details about the detected game + */ + GameDescriptor matchedGame; + + DetectedGame() : engineName(nullptr), gameId(nullptr), hasUnknownFiles(false), canBeAdded(true) {} +}; + +typedef Common::Array<DetectedGame> DetectedGames; + +/** + * Contains a list of games found by the engines' detectors. + * + * Each detected game can either: + * - be fully recognized (e.g. an exact match was found in the detection tables of an engine) + * - be an unknown variant (e.g. a game using files with the same name was found in the detection tables) + * - be recognized with unknown files (e.g. the game was exactly not found in the detection tables, + * but the detector was able to gather enough data to allow launching the game) + * + * Practically, this means a detected game can be in both the recognized game list and in the unknown game + * report handled by this class. + */ +class DetectionResults { +public: + explicit DetectionResults(const DetectedGames &detectedGames); + + /** + * List all the games that were recognized by the engines + * + * Recognized games can be added to the configuration manager and then launched. + */ + DetectedGames listRecognizedGames(); + + /** + * Were unknown game variants found by the engines? + * + * When unknown game variants are found, an unknown game report can be generated. + */ + bool foundUnknownGames() const; + + /** + * Generate a report that we found an unknown game variant, together with the file + * names, sizes and MD5 sums. + * + * @param translate translate the report to the currently active GUI language + * @param wordwrapAt word wrap the text part of the report after a number of characters + */ + Common::String generateUnknownGameReport(bool translate, uint32 wordwrapAt = 0) const; + +private: + DetectedGames _detectedGames; +}; + #endif diff --git a/engines/gob/detection/detection.cpp b/engines/gob/detection/detection.cpp index e204ced1e8..487b65f4a8 100644 --- a/engines/gob/detection/detection.cpp +++ b/engines/gob/detection/detection.cpp @@ -35,7 +35,7 @@ public: virtual GameDescriptor findGame(const char *gameId) const; - virtual const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const; + ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const override; virtual const char *getName() const; virtual const char *getOriginalCopyright() const; @@ -63,25 +63,22 @@ GameDescriptor GobMetaEngine::findGame(const char *gameId) const { return Engines::findGameID(gameId, _gameIds, obsoleteGameIDsTable); } -const ADGameDescription *GobMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { - ADFilePropertiesMap filesProps; +ADDetectedGame GobMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { + ADDetectedGame detectedGame = detectGameFilebased(allFiles, fslist, Gob::fileBased); + if (!detectedGame.desc) { + return ADDetectedGame(); + } - const Gob::GOBGameDescription *game; - game = (const Gob::GOBGameDescription *)detectGameFilebased(allFiles, fslist, Gob::fileBased, &filesProps); - if (!game) - return 0; + const Gob::GOBGameDescription *game = (const Gob::GOBGameDescription *)detectedGame.desc; if (game->gameType == Gob::kGameTypeOnceUponATime) { game = detectOnceUponATime(fslist); - if (!game) - return 0; + if (game) { + detectedGame.desc = &game->desc; + } } - ADGameIdList gameIds; - gameIds.push_back(game->desc.gameId); - - reportUnknown(fslist.begin()->getParent(), filesProps, gameIds); - return (const ADGameDescription *)game; + return detectedGame; } const Gob::GOBGameDescription *GobMetaEngine::detectOnceUponATime(const Common::FSList &fslist) { diff --git a/engines/made/detection.cpp b/engines/made/detection.cpp index 636c2d147c..bf05385c88 100644 --- a/engines/made/detection.cpp +++ b/engines/made/detection.cpp @@ -535,7 +535,7 @@ public: virtual bool hasFeature(MetaEngineFeature f) const; virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; - const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const; + ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const override; }; @@ -557,7 +557,7 @@ bool MadeMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGame return gd != 0; } -const ADGameDescription *MadeMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { +ADDetectedGame MadeMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { // Set the default values for the fallback descriptor's ADGameDescription part. Made::g_fallbackDesc.desc.language = Common::UNK_LANG; Made::g_fallbackDesc.desc.platform = Common::kPlatformDOS; @@ -569,7 +569,7 @@ const ADGameDescription *MadeMetaEngine::fallbackDetect(const FileMap &allFiles, Made::g_fallbackDesc.version = 3; //return (const ADGameDescription *)&Made::g_fallbackDesc; - return NULL; + return ADDetectedGame(); } #if PLUGIN_ENABLED_DYNAMIC(MADE) diff --git a/engines/metaengine.h b/engines/metaengine.h index 68f4b36848..9a0280d116 100644 --- a/engines/metaengine.h +++ b/engines/metaengine.h @@ -79,7 +79,7 @@ public: * (possibly empty) list of games supported by the engine which it was able * to detect amongst the given files. */ - virtual GameList detectGames(const Common::FSList &fslist, bool useUnknownGameDialog = false) const = 0; + virtual DetectedGames detectGames(const Common::FSList &fslist) const = 0; /** * Tries to instantiate an engine instance based on the settings of @@ -269,7 +269,7 @@ class EngineManager : public Common::Singleton<EngineManager> { public: GameDescriptor findGameInLoadedPlugins(const Common::String &gameName, const Plugin **plugin = NULL) const; GameDescriptor findGame(const Common::String &gameName, const Plugin **plugin = NULL) const; - GameList detectGames(const Common::FSList &fslist, bool useUnknownGameDialog = false) const; + DetectionResults detectGames(const Common::FSList &fslist) const; const PluginList &getPlugins() const; }; diff --git a/engines/mohawk/detection.cpp b/engines/mohawk/detection.cpp index 82f901d9ad..94ca4f0513 100644 --- a/engines/mohawk/detection.cpp +++ b/engines/mohawk/detection.cpp @@ -184,7 +184,7 @@ public: _directoryGlobs = directoryGlobs; } - const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const override { + ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const override { return detectGameFilebased(allFiles, fslist, Mohawk::fileBased); } diff --git a/engines/queen/detection.cpp b/engines/queen/detection.cpp index aed8b7dcb1..f7cde05fbd 100644 --- a/engines/queen/detection.cpp +++ b/engines/queen/detection.cpp @@ -447,7 +447,7 @@ public: virtual int getMaximumSaveSlot() const { return 99; } virtual void removeSaveState(const char *target, int slot) const; - const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const; + ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const override; }; bool QueenMetaEngine::hasFeature(MetaEngineFeature f) const { @@ -457,7 +457,7 @@ bool QueenMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSupportsDeleteSave); } -const ADGameDescription *QueenMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { +ADDetectedGame QueenMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { static ADGameDescription desc; // Iterate over all files in the given directory @@ -492,11 +492,13 @@ const ADGameDescription *QueenMetaEngine::fallbackDetect(const FileMap &allFiles desc.extra = "Talkie"; desc.guiOptions = GAMEOPTION_ALT_INTRO; } - return (const ADGameDescription *)&desc; + + return ADDetectedGame(&desc); } } } - return 0; + + return ADDetectedGame(); } SaveStateList QueenMetaEngine::listSaves(const char *target) const { diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp index bc7241269d..9a70429e47 100644 --- a/engines/sci/detection.cpp +++ b/engines/sci/detection.cpp @@ -562,7 +562,7 @@ public: } virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const; - const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const; + ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const override; virtual bool hasFeature(MetaEngineFeature f) const; virtual SaveStateList listSaves(const char *target) const; virtual int getMaximumSaveSlot() const; @@ -590,7 +590,7 @@ Common::Language charToScummVMLanguage(const char c) { } } -const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { +ADDetectedGame SciMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { bool foundResMap = false; bool foundRes000 = false; @@ -647,7 +647,7 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles, // If these files aren't found, it can't be SCI if (!foundResMap && !foundRes000) - return 0; + return ADDetectedGame(); ResourceManager resMan(true); resMan.addAppropriateSourcesForDetection(fslist); @@ -658,7 +658,7 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles, // Is SCI32 compiled in? If not, and this is a SCI32 game, // stop here if (getSciVersionForDetection() >= SCI_VERSION_2) - return 0; + return ADDetectedGame(); #endif ViewType gameViews = resMan.getViewType(); @@ -667,7 +667,7 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles, // Can't be SCI (or unsupported SCI views). Pinball Creep by Sierra also uses resource.map/resource.000 files // but doesn't share SCI format at all if (gameViews == kViewUnknown) - return 0; + return ADDetectedGame(); // Set the platform to Amiga if the game is using Amiga views if (gameViews == kViewAmiga) @@ -678,7 +678,7 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles, // If we don't have a game id, the game is not SCI if (sierraGameId.empty()) - return 0; + return ADDetectedGame(); Common::String gameId = convertSierraGameId(sierraGameId, &s_fallbackDesc.flags, resMan); strncpy(s_fallbackGameIdBuf, gameId.c_str(), sizeof(s_fallbackGameIdBuf) - 1); @@ -753,7 +753,7 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles, s_fallbackDesc.extra = "CD"; } - return &s_fallbackDesc; + return ADDetectedGame(&s_fallbackDesc); } bool SciMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { diff --git a/engines/scumm/detection.cpp b/engines/scumm/detection.cpp index 9573db55cd..078cc8de27 100644 --- a/engines/scumm/detection.cpp +++ b/engines/scumm/detection.cpp @@ -961,7 +961,7 @@ public: virtual bool hasFeature(MetaEngineFeature f) const; virtual GameList getSupportedGames() const; virtual GameDescriptor findGame(const char *gameid) const; - virtual GameList detectGames(const Common::FSList &fslist, bool useUnknownGameDialog = false) const; + virtual DetectedGames detectGames(const Common::FSList &fslist) const override; virtual Common::Error createInstance(OSystem *syst, Engine **engine) const; @@ -1026,29 +1026,30 @@ static Common::String generatePreferredTarget(const DetectorResult &x) { return res; } -GameList ScummMetaEngine::detectGames(const Common::FSList &fslist, bool /*useUnknownGameDialog*/) const { - GameList detectedGames; +DetectedGames ScummMetaEngine::detectGames(const Common::FSList &fslist) const { + DetectedGames detectedGames; Common::List<DetectorResult> results; - ::detectGames(fslist, results, 0); for (Common::List<DetectorResult>::iterator x = results.begin(); x != results.end(); ++x) { const PlainGameDescriptor *g = findPlainGameDescriptor(x->game.gameid, gameDescriptions); assert(g); - GameDescriptor dg(x->game.gameid, g->description, x->language, x->game.platform); + + DetectedGame game; + game.matchedGame = GameDescriptor(x->game.gameid, g->description, x->language, x->game.platform); // Append additional information, if set, to the description. - dg.updateDesc(x->extra); + game.matchedGame.updateDesc(x->extra); // Compute and set the preferred target name for this game. // Based on generateComplexID() in advancedDetector.cpp. - dg["preferredtarget"] = generatePreferredTarget(*x); + game.matchedGame["preferredtarget"] = generatePreferredTarget(*x); - dg.setGUIOptions(x->game.guioptions + MidiDriver::musicType2GUIO(x->game.midi)); - dg.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(x->language)); + game.matchedGame.setGUIOptions(x->game.guioptions + MidiDriver::musicType2GUIO(x->game.midi)); + game.matchedGame.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(x->language)); - detectedGames.push_back(dg); + detectedGames.push_back(game); } return detectedGames; diff --git a/engines/sky/detection.cpp b/engines/sky/detection.cpp index a74b63fb8c..cc1f1adc82 100644 --- a/engines/sky/detection.cpp +++ b/engines/sky/detection.cpp @@ -79,7 +79,7 @@ public: virtual GameList getSupportedGames() const; virtual const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const; virtual GameDescriptor findGame(const char *gameid) const; - virtual GameList detectGames(const Common::FSList &fslist, bool useUnknownGameDialog = false) const; + DetectedGames detectGames(const Common::FSList &fslist) const override; virtual Common::Error createInstance(OSystem *syst, Engine **engine) const; @@ -141,8 +141,8 @@ GameDescriptor SkyMetaEngine::findGame(const char *gameid) const { return GameDescriptor(); } -GameList SkyMetaEngine::detectGames(const Common::FSList &fslist, bool /*useUnknownGameDialog*/) const { - GameList detectedGames; +DetectedGames SkyMetaEngine::detectGames(const Common::FSList &fslist) const { + DetectedGames detectedGames; bool hasSkyDsk = false; bool hasSkyDnr = false; int dinnerTableEntries = -1; @@ -173,18 +173,19 @@ GameList SkyMetaEngine::detectGames(const Common::FSList &fslist, bool /*useUnkn // 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(skySetting.gameId, skySetting.description, Common::UNK_LANG, Common::kPlatformUnknown); + DetectedGame game; + game.matchedGame = GameDescriptor(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); + game.matchedGame.updateDesc(Common::String::format("v0.0%d %s", sv->version, sv->extraDesc).c_str()); + game.matchedGame.setGUIOptions(sv->guioptions); break; } ++sv; } - detectedGames.push_back(dg); + detectedGames.push_back(game); } return detectedGames; diff --git a/engines/sludge/detection.cpp b/engines/sludge/detection.cpp index a530a5c796..85c0f22907 100644 --- a/engines/sludge/detection.cpp +++ b/engines/sludge/detection.cpp @@ -100,10 +100,10 @@ public: } // for fall back detection - virtual const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const; + ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const override; }; -const ADGameDescription *SludgeMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { +ADDetectedGame SludgeMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { // reset fallback description s_fallbackDesc.desc.gameId = "sludge"; s_fallbackDesc.desc.extra = ""; @@ -147,9 +147,19 @@ const ADGameDescription *SludgeMetaEngine::fallbackDetect(const FileMap &allFile s_fallbackFileNameBuffer[50] = '\0'; s_fallbackDesc.desc.filesDescriptions[0].fileName = s_fallbackFileNameBuffer; - return (const ADGameDescription *)&s_fallbackDesc; + ADDetectedGame game; + game.desc = &s_fallbackDesc.desc; + + FileProperties tmp; + if (getFileProperties(file->getParent(), allFiles, s_fallbackDesc.desc, fileName, tmp)) { + game.hasUnknownFiles = true; + game.matchedFiles[fileName] = tmp; + } + + return game; } - return 0; + + return ADDetectedGame(); } #if PLUGIN_ENABLED_DYNAMIC(SLUDGE) diff --git a/engines/sword1/detection.cpp b/engines/sword1/detection.cpp index ddfc4b8c14..7fb86fec0b 100644 --- a/engines/sword1/detection.cpp +++ b/engines/sword1/detection.cpp @@ -89,7 +89,7 @@ public: virtual bool hasFeature(MetaEngineFeature f) const; virtual GameList getSupportedGames() const; virtual GameDescriptor findGame(const char *gameid) const; - virtual GameList detectGames(const Common::FSList &fslist, bool useUnknownGameDialog = false) const; + DetectedGames detectGames(const Common::FSList &fslist) const override; virtual SaveStateList listSaves(const char *target) const; virtual int getMaximumSaveSlot() const; virtual void removeSaveState(const char *target, int slot) const; @@ -175,9 +175,9 @@ void Sword1CheckDirectory(const Common::FSList &fslist, bool *filesFound, bool r } } -GameList SwordMetaEngine::detectGames(const Common::FSList &fslist, bool /*useUnknownGameDialog*/) const { +DetectedGames SwordMetaEngine::detectGames(const Common::FSList &fslist) const { int i, j; - GameList detectedGames; + DetectedGames detectedGames; bool filesFound[NUM_FILES_TO_CHECK]; for (i = 0; i < NUM_FILES_TO_CHECK; i++) filesFound[i] = false; @@ -212,31 +212,31 @@ GameList SwordMetaEngine::detectGames(const Common::FSList &fslist, bool /*useUn if (!filesFound[i] || psxFilesFound) psxDemoFilesFound = false; - GameDescriptor gd; + DetectedGame game; if (mainFilesFound && pcFilesFound && demoFilesFound) - gd = GameDescriptor(sword1DemoSettings, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); + game.matchedGame = GameDescriptor(sword1DemoSettings, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); else if (mainFilesFound && pcFilesFound && psxFilesFound) - gd = GameDescriptor(sword1PSXSettings, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); + game.matchedGame = GameDescriptor(sword1PSXSettings, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); else if (mainFilesFound && pcFilesFound && psxDemoFilesFound) - gd = GameDescriptor(sword1PSXDemoSettings, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); + game.matchedGame = GameDescriptor(sword1PSXDemoSettings, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); else if (mainFilesFound && pcFilesFound && !psxFilesFound) - gd = GameDescriptor(sword1FullSettings, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); + game.matchedGame = GameDescriptor(sword1FullSettings, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); else if (mainFilesFound && macFilesFound) - gd = GameDescriptor(sword1MacFullSettings, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); + game.matchedGame = GameDescriptor(sword1MacFullSettings, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); else if (mainFilesFound && macDemoFilesFound) - gd = GameDescriptor(sword1MacDemoSettings, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); + game.matchedGame = GameDescriptor(sword1MacDemoSettings, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); else return detectedGames; - gd.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::EN_ANY)); - gd.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::DE_DEU)); - gd.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::FR_FRA)); - gd.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::IT_ITA)); - gd.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::ES_ESP)); - gd.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::PT_BRA)); - gd.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::CZ_CZE)); + game.matchedGame.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::EN_ANY)); + game.matchedGame.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::DE_DEU)); + game.matchedGame.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::FR_FRA)); + game.matchedGame.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::IT_ITA)); + game.matchedGame.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::ES_ESP)); + game.matchedGame.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::PT_BRA)); + game.matchedGame.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::CZ_CZE)); - detectedGames.push_back(gd); + detectedGames.push_back(game); return detectedGames; } diff --git a/engines/sword2/sword2.cpp b/engines/sword2/sword2.cpp index 10ddda7d2e..27fcc74ad5 100644 --- a/engines/sword2/sword2.cpp +++ b/engines/sword2/sword2.cpp @@ -95,7 +95,7 @@ public: virtual GameList getSupportedGames() const; virtual const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const; virtual GameDescriptor findGame(const char *gameid) const; - virtual GameList detectGames(const Common::FSList &fslist, bool useUnknownGameDialog = false) const; + virtual DetectedGames detectGames(const Common::FSList &fslist) const; virtual SaveStateList listSaves(const char *target) const; virtual int getMaximumSaveSlot() const; virtual void removeSaveState(const char *target, int slot) const; @@ -148,7 +148,7 @@ GameDescriptor Sword2MetaEngine::findGame(const char *gameid) const { bool isFullGame(const Common::FSList &fslist) { Common::FSList::const_iterator file; - // We distinguish between the two versions by the presense of paris.clu + // We distinguish between the two versions by the presence of paris.clu for (file = fslist.begin(); file != fslist.end(); ++file) { if (!file->isDirectory()) { if (file->getName().equalsIgnoreCase("paris.clu")) @@ -159,8 +159,8 @@ bool isFullGame(const Common::FSList &fslist) { return false; } -GameList detectGamesImpl(const Common::FSList &fslist, bool recursion = false) { - GameList detectedGames; +DetectedGames detectGamesImpl(const Common::FSList &fslist, bool recursion = false) { + DetectedGames detectedGames; const Sword2::GameSettings *g; Common::FSList::const_iterator file; bool isFullVersion = isFullGame(fslist); @@ -192,7 +192,10 @@ GameList detectGamesImpl(const Common::FSList &fslist, bool recursion = false) { 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))); + DetectedGame game; + game.matchedGame = GameDescriptor(g->gameid, g->description, Common::UNK_LANG, Common::kPlatformUnknown, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); + + detectedGames.push_back(game); break; } } @@ -208,7 +211,7 @@ GameList detectGamesImpl(const Common::FSList &fslist, bool recursion = false) { if (file->getName().equalsIgnoreCase("clusters")) { Common::FSList recList; if (file->getChildren(recList, Common::FSNode::kListAll)) { - GameList recGames(detectGamesImpl(recList, true)); + DetectedGames recGames = detectGamesImpl(recList, true); if (!recGames.empty()) { detectedGames.push_back(recGames); break; @@ -223,7 +226,7 @@ GameList detectGamesImpl(const Common::FSList &fslist, bool recursion = false) { return detectedGames; } -GameList Sword2MetaEngine::detectGames(const Common::FSList &fslist, bool /*useUnknownGameDialog*/) const { +DetectedGames Sword2MetaEngine::detectGames(const Common::FSList &fslist) const { return detectGamesImpl(fslist); } @@ -278,10 +281,10 @@ Common::Error Sword2MetaEngine::createInstance(OSystem *syst, Engine **engine) c // Invoke the detector Common::String gameid = ConfMan.get("gameid"); - GameList detectedGames = detectGames(fslist); + DetectedGames detectedGames = detectGames(fslist); for (uint i = 0; i < detectedGames.size(); i++) { - if (detectedGames[i].gameid() == gameid) { + if (detectedGames[i].matchedGame.gameid() == gameid) { *engine = new Sword2::Sword2Engine(syst); return Common::kNoError; } diff --git a/engines/tinsel/detection.cpp b/engines/tinsel/detection.cpp index d6bcfe5ea0..1c60c5eb8a 100644 --- a/engines/tinsel/detection.cpp +++ b/engines/tinsel/detection.cpp @@ -97,7 +97,7 @@ public: } virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; - const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const; + ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const override; virtual bool hasFeature(MetaEngineFeature f) const; virtual SaveStateList listSaves(const char *target) const; @@ -185,7 +185,7 @@ typedef Common::Array<const ADGameDescription *> ADGameDescList; * 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 FileMap &allFilesXXX, const Common::FSList &fslist) const { +ADDetectedGame TinselMetaEngine::fallbackDetect(const FileMap &allFilesXXX, const Common::FSList &fslist) const { Common::String extra; FileMap allFiles; SizeMD5Map filesSizeMD5; @@ -194,7 +194,7 @@ const ADGameDescription *TinselMetaEngine::fallbackDetect(const FileMap &allFile const Tinsel::TinselGameDescription *g; if (fslist.empty()) - return NULL; + return ADDetectedGame(); // TODO: The following code is essentially a slightly modified copy of the // complete code of function detectGame() in engines/advancedDetector.cpp. @@ -262,7 +262,7 @@ const ADGameDescription *TinselMetaEngine::fallbackDetect(const FileMap &allFile } } - ADGameDescList matched; + ADDetectedGame matched; int maxFilesMatched = 0; // MD5 based matching @@ -310,22 +310,15 @@ const ADGameDescription *TinselMetaEngine::fallbackDetect(const FileMap &allFile for (fileDesc = g->desc.filesDescriptions; fileDesc->fileName; fileDesc++) curFilesMatched++; - if (curFilesMatched > maxFilesMatched) { + 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); + matched = ADDetectedGame(&g->desc); } } } - // We didn't find a match - if (matched.empty()) - return NULL; - - return *matched.begin(); + return matched; } int TinselMetaEngine::getMaximumSaveSlot() const { return 99; } diff --git a/engines/toon/detection.cpp b/engines/toon/detection.cpp index 6eb38c4883..634d286c7c 100644 --- a/engines/toon/detection.cpp +++ b/engines/toon/detection.cpp @@ -132,7 +132,7 @@ public: _directoryGlobs = directoryGlobs; } - virtual const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { + ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const override { return detectGameFilebased(allFiles, fslist, Toon::fileBasedFallback); } diff --git a/engines/touche/detection.cpp b/engines/touche/detection.cpp index dcb58ffae6..51b17b26d9 100644 --- a/engines/touche/detection.cpp +++ b/engines/touche/detection.cpp @@ -133,15 +133,8 @@ public: _directoryGlobs = directoryGlobs; } - virtual const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { - ADFilePropertiesMap filesProps; - - const ADGameDescription *matchedDesc = detectGameFilebased(allFiles, fslist, Touche::fileBasedFallback, &filesProps); - if (!matchedDesc) - return 0; - - reportUnknown(fslist.begin()->getParent(), filesProps); - return matchedDesc; + ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const override { + return detectGameFilebased(allFiles, fslist, Touche::fileBasedFallback); } virtual const char *getName() const { diff --git a/engines/tucker/detection.cpp b/engines/tucker/detection.cpp index 119d60f23a..2318947b12 100644 --- a/engines/tucker/detection.cpp +++ b/engines/tucker/detection.cpp @@ -149,18 +149,19 @@ public: return desc != 0; } - virtual const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { + virtual ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const override { 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 ADDetectedGame(&tuckerDemoGameDescription); } } } } - return 0; + + return ADDetectedGame(); } virtual SaveStateList listSaves(const char *target) const { diff --git a/engines/unknown-game-dialog.cpp b/engines/unknown-game-dialog.cpp index f737eb1c67..1b7dd73a92 100644 --- a/engines/unknown-game-dialog.cpp +++ b/engines/unknown-game-dialog.cpp @@ -20,14 +20,16 @@ * */ +#include "engines/unknown-game-dialog.h" + #include "common/translation.h" #include "common/str-array.h" #include "common/system.h" + #include "gui/gui-manager.h" #include "gui/message.h" #include "gui/ThemeEval.h" #include "gui/widgets/popup.h" -#include "engines/unknown-game-dialog.h" enum { kCopyToClipboard = 'cpcl', @@ -35,36 +37,33 @@ enum { kClose = 'clse' }; -UnknownGameDialog::UnknownGameDialog(const Common::String &reportData, const Common::String &reportTranslated, const Common::String &bugtrackerAffectedEngine) - : Dialog(30, 20, 260, 124) { - - _reportData = reportData; - _reportTranslated = reportTranslated; - _bugtrackerAffectedEngine = bugtrackerAffectedEngine; +UnknownGameDialog::UnknownGameDialog(const DetectionResults &detectionResults) : + Dialog(30, 20, 260, 124), + _detectionResults(detectionResults) { + Common::String reportTranslated = _detectionResults.generateUnknownGameReport(true); - //Check if we have clipboard functionality and expand the reportTranslated message if needed... + // Check if we have clipboard functionality and expand the reportTranslated message if needed... if (g_system->hasFeature(OSystem::kFeatureClipboardSupport)) { - _reportTranslated += "\n"; - _reportTranslated += _("Use the button below to copy the required game information into your clipboard."); + reportTranslated += "\n"; + reportTranslated += _("Use the button below to copy the required game information into your clipboard."); } #if 0 - //Check if we have support for opening URLs and expand the reportTranslated message if needed... + // Check if we have support for opening URLs and expand the reportTranslated message if needed... if (g_system->hasFeature(OSystem::kFeatureOpenUrl)) { - _reportTranslated += "\n"; - _reportTranslated += _("You can also directly report your game to the Bug Tracker!"); + reportTranslated += "\n"; + reportTranslated += _("You can also directly report your game to the Bug Tracker."); } #endif const int screenW = g_system->getOverlayWidth(); - const int screenH = g_system->getOverlayHeight(); int buttonWidth = g_gui.xmlEval()->getVar("Globals.Button.Width", 0); int buttonHeight = g_gui.xmlEval()->getVar("Globals.Button.Height", 0); - //Calculate the size the dialog needs + // Calculate the size the dialog needs Common::Array<Common::String> lines; - int maxlineWidth = g_gui.getFont().wordWrapText(_reportTranslated, screenW - 2 * 20, lines); + int maxlineWidth = g_gui.getFont().wordWrapText(reportTranslated, screenW - 2 * 20, lines); int lineCount = lines.size() + 1; _h = 3 * kLineHeight + lineCount * kLineHeight; @@ -84,7 +83,7 @@ UnknownGameDialog::UnknownGameDialog(const Common::String &reportData, const Com int buttonPos = _w - closeButtonWidth - 10; new GUI::ButtonWidget(this, buttonPos, _h - buttonHeight - 8, buttonWidth, buttonHeight, _("Close"), 0, kClose); - //Check if we have clipboard functionality + // Check if we have clipboard functionality if (g_system->hasFeature(OSystem::kFeatureClipboardSupport)) { buttonPos -= copyToClipboardButtonWidth + 5; new GUI::ButtonWidget(this, buttonPos, _h - buttonHeight - 8, copyToClipboardButtonWidth, buttonHeight, _("Copy to clipboard"), 0, kCopyToClipboard); @@ -98,15 +97,10 @@ UnknownGameDialog::UnknownGameDialog(const Common::String &reportData, const Com // https://www.scummvm.org/unknowngame?engine=Foo&description=Bar) that would // redirect to whatever our bugtracker system is. - //Check if we have support for opening URLs + // Check if we have support for opening URLs if (g_system->hasFeature(OSystem::kFeatureOpenUrl)) { buttonPos -= openBugtrackerURLButtonWidth + 5; new GUI::ButtonWidget(this, buttonPos, _h - buttonHeight - 8, openBugtrackerURLButtonWidth, buttonHeight, _("Report game"), 0, kOpenBugtrackerURL); - //Formatting the reportData for bugtracker submission [replace line breaks]... - _bugtrackerGameData = _reportData; - while (_bugtrackerGameData.contains("\n")) { - Common::replace(_bugtrackerGameData, "\n", "%0A"); - } } #endif @@ -126,27 +120,39 @@ void UnknownGameDialog::reflowLayout() { } Common::String UnknownGameDialog::generateBugtrackerURL() { - return Common::String::format(( + // TODO: Remove the filesystem path from the bugtracker report + Common::String report = _detectionResults.generateUnknownGameReport(false); + + // Formatting the report for bugtracker submission [replace line breaks]... + while (report.contains("\n")) { + Common::replace(report, "\n", "%0A"); + } + + return Common::String::format( "https://bugs.scummvm.org/newticket?" - "summary=[UNK] Unknown game for engine %s:" "&description=%s" - "&component=Engine%%3A%s" "&type=enhancement" - "&keywords=unknown-game,%s"), - _bugtrackerAffectedEngine.c_str(), _bugtrackerGameData.c_str(), _bugtrackerAffectedEngine.c_str(), _bugtrackerAffectedEngine.c_str()); + "&keywords=unknown-game", + report.c_str()); } void UnknownGameDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) { switch(cmd) { - case kCopyToClipboard: - g_system->setTextInClipboard(_reportData); - if (g_system->setTextInClipboard(_reportData)) { - g_system->displayMessageOnOSD(_("All necessary information about your game has been copied into the clipboard")); + case kCopyToClipboard: { + // TODO: Remove the filesystem path from the report + Common::String report = _detectionResults.generateUnknownGameReport(false); + + if (g_system->setTextInClipboard(report)) { + g_system->displayMessageOnOSD( + _("All necessary information about your game has been copied into the clipboard")); } else { g_system->displayMessageOnOSD(_("Copying the game information to the clipboard has failed!")); } break; + } case kClose: + // When the detection entry comes from the fallback detector, the game can be added / launched anyways. + // TODO: Add a button to cancel adding the game. And make it clear that launching the game may not work properly. close(); break; case kOpenBugtrackerURL: diff --git a/engines/unknown-game-dialog.h b/engines/unknown-game-dialog.h index 51adf27996..0878406244 100644 --- a/engines/unknown-game-dialog.h +++ b/engines/unknown-game-dialog.h @@ -22,16 +22,18 @@ #include "gui/dialog.h" +#include "engines/metaengine.h" + class UnknownGameDialog : public GUI::Dialog { public: - UnknownGameDialog(const Common::String &reportData, const Common::String &reportTranslated, const Common::String &bugtrackerAffectedEngine); - void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data); - virtual Common::String generateBugtrackerURL(); - virtual void reflowLayout(); + UnknownGameDialog(const DetectionResults &detectionResults); private: - Common::String _reportData; - Common::String _reportTranslated; - Common::String _bugtrackerGameData; - Common::String _bugtrackerAffectedEngine; + // Dialog API + void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) override; + void reflowLayout() override; + + Common::String generateBugtrackerURL(); + + const DetectionResults &_detectionResults; }; diff --git a/engines/wintermute/detection.cpp b/engines/wintermute/detection.cpp index df5cc41b10..6208d775a6 100644 --- a/engines/wintermute/detection.cpp +++ b/engines/wintermute/detection.cpp @@ -100,7 +100,7 @@ public: return "Copyright (C) 2011 Jan Nedoma"; } - virtual const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { + ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const override { // Set some defaults s_fallbackDesc.extra = ""; s_fallbackDesc.language = Common::UNK_LANG; @@ -130,10 +130,12 @@ public: s_fallbackDesc.extra = offset; s_fallbackDesc.flags |= ADGF_USEEXTRAASTITLE; } - return &s_fallbackDesc; + + return ADDetectedGame(&s_fallbackDesc); } // Fall through to return 0; } - return 0; + + return ADDetectedGame(); } virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { diff --git a/gui/launcher.cpp b/gui/launcher.cpp index 857d7d001a..b14be6c11e 100644 --- a/gui/launcher.cpp +++ b/gui/launcher.cpp @@ -45,6 +45,7 @@ #include "gui/EventRecorder.h" #endif #include "gui/saveload.h" +#include "engines/unknown-game-dialog.h" #include "gui/widgets/edittext.h" #include "gui/widgets/list.h" #include "gui/widgets/tab.h" @@ -573,7 +574,17 @@ bool LauncherDialog::doGameDetection(const Common::String &path) { // ...so let's determine a list of candidates, games that // could be contained in the specified directory. - GameList candidates(EngineMan.detectGames(files, true)); + DetectionResults detectionResults = EngineMan.detectGames(files); + + if (detectionResults.foundUnknownGames()) { + Common::String report = detectionResults.generateUnknownGameReport(false, 80); + g_system->logMessage(LogMessageType::kInfo, report.c_str()); + + UnknownGameDialog dialog(detectionResults); + dialog.runModal(); + } + + Common::Array<DetectedGame> candidates = detectionResults.listRecognizedGames(); int idx; if (candidates.empty()) { @@ -589,17 +600,14 @@ bool LauncherDialog::doGameDetection(const Common::String &path) { // Display the candidates to the user and let her/him pick one StringArray list; for (idx = 0; idx < (int)candidates.size(); idx++) - list.push_back(candidates[idx].description()); + list.push_back(candidates[idx].matchedGame.description()); ChooserDialog dialog(_("Pick the game:")); dialog.setList(list); idx = dialog.runModal(); } if (0 <= idx && idx < (int)candidates.size()) { - GameDescriptor result = candidates[idx]; - - // TODO: Change the detectors to set "path" ! - result["path"] = dir.getPath(); + const GameDescriptor &result = candidates[idx].matchedGame; Common::String domain = addGameToConf(result); diff --git a/gui/massadd.cpp b/gui/massadd.cpp index db569fac82..2774d476db 100644 --- a/gui/massadd.cpp +++ b/gui/massadd.cpp @@ -187,7 +187,12 @@ void MassAddDialog::handleTickle() { } // Run the detector on the dir - GameList candidates(EngineMan.detectGames(files)); + DetectionResults detectionResults = EngineMan.detectGames(files); + + if (detectionResults.foundUnknownGames()) { + Common::String report = detectionResults.generateUnknownGameReport(false, 80); + g_system->logMessage(LogMessageType::kInfo, report.c_str()); + } // Just add all detected games / game variants. If we get more than one, // that either means the directory contains multiple games, or the detector @@ -195,8 +200,9 @@ void MassAddDialog::handleTickle() { // 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; + DetectedGames candidates = detectionResults.listRecognizedGames(); + for (DetectedGames::const_iterator cand = candidates.begin(); cand != candidates.end(); ++cand) { + const GameDescriptor &result = cand->matchedGame; Common::String path = dir.getPath(); // Remove trailing slashes @@ -224,7 +230,6 @@ void MassAddDialog::handleTickle() { break; // Skip duplicates } } - result["path"] = path; _games.push_back(result); _list->append(result.description()); |