diff options
author | Bastien Bouclet | 2018-05-28 18:43:15 +0200 |
---|---|---|
committer | GitHub | 2018-05-28 18:43:15 +0200 |
commit | 61f9398b04a4bce397a8be6ae96491a2015a6da2 (patch) | |
tree | 478c127f74b21365255b31d11e455bfdbb463244 | |
parent | 8d654285cbf0bf6423676244c89833557f811e38 (diff) | |
parent | 1dcb8076db64420ab28722a73583f89b38314e71 (diff) | |
download | scummvm-rg350-61f9398b04a4bce397a8be6ae96491a2015a6da2.tar.gz scummvm-rg350-61f9398b04a4bce397a8be6ae96491a2015a6da2.tar.bz2 scummvm-rg350-61f9398b04a4bce397a8be6ae96491a2015a6da2.zip |
Merge pull request #1187 from bgK/detection-refactor-unknown
ENGINES: Return unknown game variants with the list of detected games
44 files changed, 958 insertions, 834 deletions
diff --git a/backends/platform/dc/selector.cpp b/backends/platform/dc/selector.cpp index 033f06e32d..3192f3e8cf 100644 --- a/backends/platform/dc/selector.cpp +++ b/backends/platform/dc/selector.cpp @@ -271,21 +271,21 @@ static int findGames(Game *games, int max, bool use_ini) } if (!use_ini) { - GameList candidates = EngineMan.detectGames(files); + DetectedGames candidates = EngineMan.detectGames(files); - for (GameList::const_iterator ge = candidates.begin(); + for (DetectedGames::const_iterator ge = candidates.begin(); ge != candidates.end(); ++ge) if (curr_game < max) { - strcpy(games[curr_game].filename_base, ge->gameid().c_str()); + strcpy(games[curr_game].filename_base, ge->gameId.c_str()); strcpy(games[curr_game].dir, dirs[curr_dir-1].name); - games[curr_game].language = ge->language(); - games[curr_game].platform = ge->platform(); + games[curr_game].language = ge->language; + games[curr_game].platform = ge->platform; if (uniqueGame(games[curr_game].filename_base, games[curr_game].dir, games[curr_game].language, games[curr_game].platform, games, curr_game)) { - strcpy(games[curr_game].text, ge->description().c_str()); + strcpy(games[curr_game].text, ge->description.c_str()); #if 0 printf("Registered game <%s> (l:%d p:%d) in <%s> <%s> because of <%s> <*>\n", games[curr_game].text, diff --git a/base/commandLine.cpp b/base/commandLine.cpp index 83c7b56171..96548b9129 100644 --- a/base/commandLine.cpp +++ b/base/commandLine.cpp @@ -687,9 +687,9 @@ static void listGames() { const PluginList &plugins = EngineMan.getPlugins(); for (PluginList::const_iterator iter = plugins.begin(); iter != plugins.end(); ++iter) { - GameList list = (*iter)->get<MetaEngine>().getSupportedGames(); - for (GameList::iterator v = list.begin(); v != list.end(); ++v) { - printf("%-20s %s\n", v->gameid().c_str(), v->description().c_str()); + PlainGameList list = (*iter)->get<MetaEngine>().getSupportedGames(); + for (PlainGameList::iterator v = list.begin(); v != list.end(); ++v) { + printf("%-20s %s\n", v->gameId, v->description); } } } @@ -714,10 +714,10 @@ static void listTargets() { // FIXME: At this point, we should check for a "gameid" override // to find the proper desc. In fact, the platform probably should // be taken into account, too. - Common::String gameid(name); - GameDescriptor g = EngineMan.findGame(gameid); - if (g.description().size() > 0) - description = g.description(); + const Common::String &gameid = name; + PlainGameDescriptor g = EngineMan.findGame(gameid); + if (g.description) + description = g.description; } targets.push_back(Common::String::format("%-20s %s", name.c_str(), description.c_str())); @@ -770,7 +770,7 @@ static Common::Error listSaves(const Common::String &target) { // Find the plugin that will handle the specified gameid const Plugin *plugin = nullptr; - GameDescriptor game = EngineMan.findGame(gameid, &plugin); + EngineMan.findGame(gameid, &plugin); if (!plugin) { // If the target was specified, treat this as an error, and otherwise skip it. @@ -854,61 +854,36 @@ static void listAudioDevices() { } /** Display all games in the given directory, or current directory if empty */ -static GameList getGameList(const Common::FSNode &dir) { +static DetectedGames getGameList(const Common::FSNode &dir) { Common::FSList files; // Collect all files from directory if (!dir.getChildren(files, Common::FSNode::kListAll)) { printf("Path %s does not exist or is not a directory.\n", dir.getPath().c_str()); - return GameList(); + return DetectedGames(); } // 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; - } - return candidates; -} - -static bool addGameToConf(const GameDescriptor &gd) { - const Common::String &domain = gd.preferredtarget(); + DetectionResults detectionResults = EngineMan.detectGames(files); - // If game has already been added, don't add - if (ConfMan.hasGameDomain(domain)) - return false; - - // Add the name domain - ConfMan.addGameDomain(domain); - - // Copy all non-empty key/value pairs into the new domain - for (GameDescriptor::const_iterator iter = gd.begin(); iter != gd.end(); ++iter) { - if (!iter->_value.empty() && iter->_key != "preferredtarget") - ConfMan.set(iter->_key, iter->_value, domain); + if (detectionResults.foundUnknownGames()) { + Common::String report = detectionResults.generateUnknownGameReport(false, 80); + g_system->logMessage(LogMessageType::kInfo, report.c_str()); } - // Display added game info - printf("Game Added: \n GameID: %s\n Name: %s\n Language: %s\n Platform: %s\n", - gd.gameid().c_str(), - gd.description().c_str(), - Common::getLanguageDescription(gd.language()), - Common::getPlatformDescription(gd.platform())); - - return true; + return detectionResults.listRecognizedGames(); } -static GameList recListGames(const Common::FSNode &dir, const Common::String &gameId, bool recursive) { - GameList list = getGameList(dir); +static DetectedGames recListGames(const Common::FSNode &dir, const Common::String &gameId, bool recursive) { + DetectedGames list = getGameList(dir); if (recursive) { Common::FSList files; dir.getChildren(files, Common::FSNode::kListDirectoriesOnly); for (Common::FSList::const_iterator file = files.begin(); file != files.end(); ++file) { - GameList rec = recListGames(*file, gameId, recursive); - for (GameList::const_iterator game = rec.begin(); game != rec.end(); ++game) { - if (gameId.empty() || game->gameid().c_str() == gameId) + DetectedGames rec = recListGames(*file, gameId, recursive); + for (DetectedGames::const_iterator game = rec.begin(); game != rec.end(); ++game) { + if (gameId.empty() || game->gameId == gameId) list.push_back(*game); } } @@ -922,7 +897,7 @@ static Common::String detectGames(const Common::String &path, const Common::Stri bool noPath = path.empty(); //Current directory Common::FSNode dir(path); - GameList candidates = recListGames(dir, gameId, recursive); + DetectedGames candidates = recListGames(dir, gameId, recursive); if (candidates.empty()) { printf("WARNING: ScummVM could not find any game in %s\n", dir.getPath().c_str()); @@ -937,25 +912,34 @@ static Common::String detectGames(const Common::String &path, const Common::Stri // TODO this is not especially pretty printf("ID Description Full Path\n"); printf("-------------- ---------------------------------------------------------- ---------------------------------------------------------\n"); - for (GameList::iterator v = candidates.begin(); v != candidates.end(); ++v) { - printf("%-14s %-58s %s\n", v->gameid().c_str(), v->description().c_str(), (*v)["path"].c_str()); + for (DetectedGames::const_iterator v = candidates.begin(); v != candidates.end(); ++v) { + printf("%-14s %-58s %s\n", v->gameId.c_str(), v->description.c_str(), v->path.c_str()); } - return candidates[0].gameid(); + return candidates[0].gameId; } static int recAddGames(const Common::FSNode &dir, const Common::String &game, bool recursive) { int count = 0; - GameList list = getGameList(dir); - for (GameList::iterator v = list.begin(); v != list.end(); ++v) { - if (v->gameid().c_str() != game && !game.empty()) { - printf("Found %s, only adding %s per --game option, ignoring...\n", v->gameid().c_str(), game.c_str()); - } else if (!addGameToConf(*v)) { - // TODO Is it reall the case that !addGameToConf iff already added? - printf("Found %s, but has already been added, skipping\n", v->gameid().c_str()); + DetectedGames list = getGameList(dir); + for (DetectedGames::const_iterator v = list.begin(); v != list.end(); ++v) { + if (v->gameId != game && !game.empty()) { + printf("Found %s, only adding %s per --game option, ignoring...\n", v->gameId.c_str(), game.c_str()); + } else if (ConfMan.hasGameDomain(v->preferredTarget)) { + // TODO Better check for game already added? + printf("Found %s, but has already been added, skipping\n", v->gameId.c_str()); } else { - printf("Found %s, adding...\n", v->gameid().c_str()); + Common::String target = EngineMan.createTargetForGame(*v); count++; + + // Display added game info + printf("Game Added: \n Target: %s\n GameID: %s\n Name: %s\n Language: %s\n Platform: %s\n", + target.c_str(), + v->gameId.c_str(), + v->description.c_str(), + Common::getLanguageDescription(v->language), + Common::getPlatformDescription(v->platform) + ); } } @@ -1014,11 +998,13 @@ static void runDetectorTest() { continue; } - GameList candidates(EngineMan.detectGames(files)); + DetectionResults detectionResults = EngineMan.detectGames(files); + DetectedGames candidates = detectionResults.listRecognizedGames(); + bool gameidDiffers = false; - GameList::iterator x; + DetectedGames::const_iterator x; for (x = candidates.begin(); x != candidates.end(); ++x) { - gameidDiffers |= (scumm_stricmp(gameid.c_str(), x->gameid().c_str()) != 0); + gameidDiffers |= (scumm_stricmp(gameid.c_str(), x->gameId.c_str()) != 0); } if (candidates.empty()) { @@ -1041,10 +1027,10 @@ static void runDetectorTest() { for (x = candidates.begin(); x != candidates.end(); ++x) { printf(" gameid '%s', desc '%s', language '%s', platform '%s'\n", - x->gameid().c_str(), - x->description().c_str(), - Common::getLanguageCode(x->language()), - Common::getPlatformCode(x->platform())); + x->gameId.c_str(), + x->description.c_str(), + Common::getLanguageDescription(x->language), + Common::getPlatformDescription(x->platform)); } } int total = domains.size(); @@ -1092,24 +1078,26 @@ void upgradeTargets() { Common::Platform plat = Common::parsePlatform(dom.getVal("platform")); Common::String desc(dom.getVal("description")); - GameList candidates(EngineMan.detectGames(files)); - GameDescriptor *g = 0; + DetectionResults detectionResults = EngineMan.detectGames(files); + DetectedGames candidates = detectionResults.listRecognizedGames(); + + DetectedGame *g = 0; // We proceed as follows: // * If detection failed to produce candidates, skip. // * 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; } if (candidates.size() > 1) { // Scan over all candidates, check if there is a unique match for gameid, language and platform - GameList::iterator x; + DetectedGames::iterator x; int matchesFound = 0; for (x = candidates.begin(); x != candidates.end(); ++x) { - if (x->gameid() == gameid && x->language() == lang && x->platform() == plat) { + if (x->gameId == gameid && x->language == lang && x->platform == plat) { matchesFound++; g = &(*x); } @@ -1127,27 +1115,27 @@ void upgradeTargets() { // the target referred to by dom. We update several things // Always set the gameid explicitly (in case of legacy targets) - dom["gameid"] = g->gameid(); + dom["gameid"] = g->gameId; // Always set the GUI options. The user should not modify them, and engines might // gain more features over time, so we want to keep this list up-to-date. - if (g->contains("guioptions")) { - printf(" -> update guioptions to '%s'\n", (*g)["guioptions"].c_str()); - dom["guioptions"] = (*g)["guioptions"]; + if (!g->getGUIOptions().empty()) { + printf(" -> update guioptions to '%s'\n", g->getGUIOptions().c_str()); + dom["guioptions"] = g->getGUIOptions(); } else if (dom.contains("guioptions")) { dom.erase("guioptions"); } // Update the language setting but only if none has been set yet. - if (lang == Common::UNK_LANG && g->language() != Common::UNK_LANG) { - printf(" -> set language to '%s'\n", Common::getLanguageCode(g->language())); - dom["language"] = (*g)["language"]; + if (lang == Common::UNK_LANG && g->language != Common::UNK_LANG) { + printf(" -> set language to '%s'\n", Common::getLanguageCode(g->language)); + dom["language"] = Common::getLanguageCode(g->language); } // Update the platform setting but only if none has been set yet. - if (plat == Common::kPlatformUnknown && g->platform() != Common::kPlatformUnknown) { - printf(" -> set platform to '%s'\n", Common::getPlatformCode(g->platform())); - dom["platform"] = (*g)["platform"]; + if (plat == Common::kPlatformUnknown && g->platform != Common::kPlatformUnknown) { + printf(" -> set platform to '%s'\n", Common::getPlatformCode(g->platform)); + dom["platform"] = Common::getPlatformCode(g->platform); } // TODO: We could also update the description. But not everybody will want that. @@ -1156,8 +1144,8 @@ void upgradeTargets() { // should only be updated if the user explicitly requests this. #if 0 if (desc != g->description()) { - printf(" -> update desc from '%s' to\n '%s' ?\n", desc.c_str(), g->description().c_str()); - dom["description"] = (*g)["description"]; + printf(" -> update desc from '%s' to\n '%s' ?\n", desc.c_str(), g->description.c_str()); + dom["description"] = g->description; } #endif } @@ -1254,8 +1242,8 @@ bool processSettings(Common::String &command, Common::StringMap &settings, Commo // domain (i.e. a target) matching this argument, or alternatively // whether there is a gameid matching that name. if (!command.empty()) { - GameDescriptor gd = EngineMan.findGame(command); - if (ConfMan.hasGameDomain(command) || !gd.gameid().empty()) { + PlainGameDescriptor gd = EngineMan.findGame(command); + if (ConfMan.hasGameDomain(command) || gd.gameId) { bool idCameFromCommandLine = false; // WORKAROUND: Fix for bug #1719463: "DETECTOR: Launching diff --git a/base/main.cpp b/base/main.cpp index 8e783c9776..385b8f35a9 100644 --- a/base/main.cpp +++ b/base/main.cpp @@ -128,13 +128,13 @@ static const Plugin *detectPlugin() { printf("User picked target '%s' (gameid '%s')...\n", ConfMan.getActiveDomainName().c_str(), gameid.c_str()); printf(" Looking for a plugin supporting this gameid... "); - GameDescriptor game = EngineMan.findGame(gameid, &plugin); + PlainGameDescriptor game = EngineMan.findGame(gameid, &plugin); if (plugin == 0) { printf("failed\n"); warning("%s is an invalid gameid. Use the --list-games option to list supported gameid", gameid.c_str()); } else { - printf("%s\n Starting '%s'\n", plugin->getName(), game.description().c_str()); + printf("%s\n Starting '%s'\n", plugin->getName(), game.description); } return plugin; @@ -210,7 +210,10 @@ static Common::Error runGame(const Plugin *plugin, OSystem &system, const Common Common::String caption(ConfMan.get("description")); if (caption.empty()) { - caption = EngineMan.findGame(ConfMan.get("gameid")).description(); + PlainGameDescriptor game = EngineMan.findGame(ConfMan.get("gameid")); + if (game.description) { + caption = game.description; + } } if (caption.empty()) caption = ConfMan.getActiveDomainName(); // Use the domain (=target) name diff --git a/base/plugins.cpp b/base/plugins.cpp index 852786919b..023f2f3bb3 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" @@ -457,13 +458,11 @@ DECLARE_SINGLETON(EngineManager); * For the uncached version, we first try to find the plugin using the gameId * and only if we can't find it there, we loop through the plugins. **/ -GameDescriptor EngineManager::findGame(const Common::String &gameName, const Plugin **plugin) const { - GameDescriptor result; - +PlainGameDescriptor EngineManager::findGame(const Common::String &gameName, const Plugin **plugin) const { // First look for the game using the plugins in memory. This is critical // for calls coming from inside games - result = findGameInLoadedPlugins(gameName, plugin); - if (!result.gameid().empty()) { + PlainGameDescriptor result = findGameInLoadedPlugins(gameName, plugin); + if (result.gameId) { return result; } @@ -471,7 +470,7 @@ GameDescriptor EngineManager::findGame(const Common::String &gameName, const Plu // by plugin if (PluginMan.loadPluginFromGameId(gameName)) { result = findGameInLoadedPlugins(gameName, plugin); - if (!result.gameid().empty()) { + if (result.gameId) { return result; } } @@ -480,7 +479,7 @@ GameDescriptor EngineManager::findGame(const Common::String &gameName, const Plu PluginMan.loadFirstPlugin(); do { result = findGameInLoadedPlugins(gameName, plugin); - if (!result.gameid().empty()) { + if (result.gameId) { // Update with new plugin file name PluginMan.updateConfigWithFileName(gameName); break; @@ -493,10 +492,9 @@ GameDescriptor EngineManager::findGame(const Common::String &gameName, const Plu /** * Find the game within the plugins loaded in memory **/ -GameDescriptor EngineManager::findGameInLoadedPlugins(const Common::String &gameName, const Plugin **plugin) const { +PlainGameDescriptor EngineManager::findGameInLoadedPlugins(const Common::String &gameName, const Plugin **plugin) const { // Find the GameDescriptor for this target const PluginList &plugins = getPlugins(); - GameDescriptor result; if (plugin) *plugin = 0; @@ -504,18 +502,20 @@ GameDescriptor EngineManager::findGameInLoadedPlugins(const Common::String &game PluginList::const_iterator iter; for (iter = plugins.begin(); iter != plugins.end(); ++iter) { - result = (*iter)->get<MetaEngine>().findGame(gameName.c_str()); - if (!result.gameid().empty()) { + PlainGameDescriptor pgd = (*iter)->get<MetaEngine>().findGame(gameName.c_str()); + if (pgd.gameId) { if (plugin) *plugin = *iter; - return result; + return pgd; } } - return result; + + return PlainGameDescriptor::empty(); } -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,16 +524,75 @@ 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].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); } +namespace { + +void addStringToConf(const Common::String &key, const Common::String &value, const Common::String &domain) { + if (!value.empty()) + ConfMan.set(key, value, domain); +} + +} // End of anonymous namespace + +Common::String EngineManager::createTargetForGame(const DetectedGame &game) { + // The auto detector or the user made a choice. + // Pick a domain name which does not yet exist (after all, we + // are *adding* a game to the config, not replacing). + Common::String domain = game.preferredTarget; + + assert(!domain.empty()); + if (ConfMan.hasGameDomain(domain)) { + int suffixN = 1; + Common::String gameid(domain); + + while (ConfMan.hasGameDomain(domain)) { + domain = gameid + Common::String::format("-%d", suffixN); + suffixN++; + } + } + + // Add the name domain + ConfMan.addGameDomain(domain); + + // Copy all non-empty relevant values into the new domain + addStringToConf("gameid", game.gameId, domain); + addStringToConf("description", game.description, domain); + addStringToConf("language", Common::getLanguageCode(game.language), domain); + addStringToConf("platform", Common::getPlatformCode(game.platform), domain); + addStringToConf("path", game.path, domain); + addStringToConf("extra", game.extra, domain); + addStringToConf("guioptions", game.getGUIOptions(), domain); + + // TODO: Setting the description field here has the drawback + // that the user does never notice when we upgrade our descriptions. + // It might be nice to leave this field empty, and only set it to + // a value when the user edits the description string. + // However, at this point, that's impractical. Once we have a method + // to query all backends for the proper & full description of a given + // game target, we can change this (currently, you can only query + // for the generic gameid description; it's not possible to obtain + // a description which contains extended information like language, etc.). + + return domain; +} // Music plugins 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 d258f49621..3167dd9581 100644 --- a/engines/advancedDetector.cpp +++ b/engines/advancedDetector.cpp @@ -30,37 +30,19 @@ #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" -static GameDescriptor toGameDescriptor(const ADGameDescription &g, const PlainGameDescriptor *sg) { - const char *title = 0; - const char *extra; - - if (g.flags & ADGF_USEEXTRAASTITLE) { - title = g.extra; - extra = ""; - } else { - while (sg->gameId) { - if (!scumm_stricmp(g.gameId, sg->gameId)) - title = sg->description; - sg++; - } +static Common::String sanitizeName(const char *name) { + Common::String res; - extra = g.extra; + while (*name) { + if (Common::isAlnum(*name)) + res += tolower(*name); + name++; } - GameSupportLevel gsl = kStableGame; - if (g.flags & ADGF_UNSTABLE) - gsl = kUnstableGame; - else if (g.flags & ADGF_TESTING) - gsl = kTestingGame; - - GameDescriptor gd(g.gameId, title, g.language, g.platform, 0, gsl); - gd.updateDesc(extra); - return gd; + return res; } /** @@ -69,8 +51,14 @@ static GameDescriptor toGameDescriptor(const ADGameDescription &g, const PlainGa * or (if ADGF_DEMO has been set) * GAMEID-demo-PLAFORM-LANG */ -static Common::String generatePreferredTarget(const Common::String &id, const ADGameDescription *desc) { - Common::String res(id); +static Common::String generatePreferredTarget(const ADGameDescription *desc) { + Common::String res; + + if (desc->flags & ADGF_AUTOGENTARGET && desc->extra && *desc->extra) { + res = sanitizeName(desc->extra); + } else { + res = desc->gameId; + } if (desc->flags & ADGF_DEMO) { res = res + "-demo"; @@ -91,49 +79,50 @@ static Common::String generatePreferredTarget(const Common::String &id, const AD return res; } -static Common::String sanitizeName(const char *name) { - Common::String res; +DetectedGame AdvancedMetaEngine::toDetectedGame(const ADDetectedGame &adGame) const { + const ADGameDescription *desc = adGame.desc; - while (*name) { - if (Common::isAlnum(*name)) - res += tolower(*name); - name++; - } + const char *gameId = _singleId ? _singleId : desc->gameId; - return res; -} - -void AdvancedMetaEngine::updateGameDescriptor(GameDescriptor &desc, const ADGameDescription *realDesc) const { - if (_singleId != NULL) { - desc["preferredtarget"] = desc["gameid"]; - desc["gameid"] = _singleId; + const char *title; + const char *extra; + if (desc->flags & ADGF_USEEXTRAASTITLE) { + title = desc->extra; + extra = ""; + } else { + const PlainGameDescriptor *pgd = findPlainGameDescriptor(desc->gameId, _gameIds); + title = pgd->description; + extra = desc->extra; } - if (!desc.contains("preferredtarget")) - desc["preferredtarget"] = desc["gameid"]; + DetectedGame game(gameId, title, desc->language, desc->platform, extra); + game.hasUnknownFiles = adGame.hasUnknownFiles; + game.matchedFiles = adGame.matchedFiles; + game.preferredTarget = generatePreferredTarget(desc); - if (realDesc->flags & ADGF_AUTOGENTARGET) { - if (*realDesc->extra) - desc["preferredtarget"] = sanitizeName(realDesc->extra); - } + game.gameSupportLevel = kStableGame; + if (desc->flags & ADGF_UNSTABLE) + game.gameSupportLevel = kUnstableGame; + else if (desc->flags & ADGF_TESTING) + game.gameSupportLevel = kTestingGame; - desc["preferredtarget"] = generatePreferredTarget(desc["preferredtarget"], realDesc); + game.setGUIOptions(desc->guiOptions + _guiOptions); + game.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(desc->language)); - if (_flags & kADFlagUseExtraAsHint) - desc["extra"] = realDesc->extra; + if (desc->flags & ADGF_ADDENGLISH) + game.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::EN_ANY)); - desc.setGUIOptions(realDesc->guiOptions + _guiOptions); - desc.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(realDesc->language)); + if (_flags & kADFlagUseExtraAsHint) + game.extra = desc->extra; - if (realDesc->flags & ADGF_ADDENGLISH) - desc.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::EN_ANY)); + return game; } -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 +139,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 != 0) { - 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 +216,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 +265,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); + DetectedGame gameDescriptor = toDetectedGame(agdDesc); bool showTestingWarning = false; @@ -313,72 +309,20 @@ Common::Error AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) showTestingWarning = true; #endif - if (((gameDescriptor.getSupportLevel() == kUnstableGame - || (gameDescriptor.getSupportLevel() == kTestingGame + if (((gameDescriptor.gameSupportLevel == kUnstableGame + || (gameDescriptor.gameSupportLevel == kTestingGame && showTestingWarning))) && !Engine::warnUserAboutUnsupportedGame()) return Common::kUserCanceled; - debug(2, "Running %s", gameDescriptor.description().c_str()); - initSubSystems(agdDesc); - if (!createInstance(syst, engine, agdDesc)) + debug(2, "Running %s", gameDescriptor.description.c_str()); + 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.size()) { - 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 +363,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 +393,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; @@ -460,12 +405,12 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons // 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) { + for (descPtr = _gameDescriptors; ((const ADGameDescription *)descPtr)->gameId != nullptr; descPtr += _descItemSize) { g = (const ADGameDescription *)descPtr; for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) { Common::String fname = fileDesc->fileName; - ADFileProperties tmp; + FileProperties tmp; if (filesProps.contains(fname)) continue; @@ -477,16 +422,13 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons } } - 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) { + 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 +441,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 != NULL && fileDesc->md5 != filesProps[tstr].md5) { + 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 +484,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.size() || 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 +498,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,35 +541,36 @@ 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 { +PlainGameList AdvancedMetaEngine::getSupportedGames() const { if (_singleId != NULL) { - GameList gl; + PlainGameList gl; const PlainGameDescriptor *g = _gameIds; while (g->gameId) { if (0 == scumm_stricmp(_singleId, g->gameId)) { - gl.push_back(GameDescriptor(g->gameId, g->description)); + gl.push_back(*g); return gl; } @@ -646,17 +579,17 @@ GameList AdvancedMetaEngine::getSupportedGames() const { error("Engine %s doesn't have its singleid specified in ids list", _singleId); } - return GameList(_gameIds); + return PlainGameList(_gameIds); } -GameDescriptor AdvancedMetaEngine::findGame(const char *gameId) const { +PlainGameDescriptor AdvancedMetaEngine::findGame(const char *gameId) const { // First search the list of supported gameids for a match. const PlainGameDescriptor *g = findPlainGameDescriptor(gameId, _gameIds); if (g) - return GameDescriptor(*g); + return *g; // No match found - return GameDescriptor(); + return PlainGameDescriptor::empty(); } AdvancedMetaEngine::AdvancedMetaEngine(const void *descs, uint descItemSize, const PlainGameDescriptor *gameIds, const ADExtraGuiOptionsMap *extraGuiOptions) diff --git a/engines/advancedDetector.h b/engines/advancedDetector.h index d7e85f86fe..326cb79c49 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 @@ -274,11 +265,11 @@ public: * Returns list of targets supported by the engine. * Distinguishes engines with single ID */ - virtual GameList getSupportedGames() const; + PlainGameList getSupportedGames() const override; - virtual GameDescriptor findGame(const char *gameId) const; + PlainGameDescriptor findGame(const char *gameId) const override; - 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,16 +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; - - // TODO - void updateGameDescriptor(GameDescriptor &desc, const ADGameDescription *realDesc) const; + ADDetectedGame detectGameFilebased(const FileMap &allFiles, const Common::FSList &fslist, const ADFileBasedFallback *fileBasedFallback) const; /** * Compose a hashmap of all files in fslist. @@ -345,7 +327,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/agos/detection.cpp b/engines/agos/detection.cpp index dbc4ee9145..1847434200 100644 --- a/engines/agos/detection.cpp +++ b/engines/agos/detection.cpp @@ -99,7 +99,7 @@ public: _directoryGlobs = directoryGlobs; } - virtual GameDescriptor findGame(const char *gameId) const { + PlainGameDescriptor findGame(const char *gameId) const override { return Engines::findGameID(gameId, _gameIds, obsoleteGameIDsTable); } 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/cine/detection.cpp b/engines/cine/detection.cpp index 6c8b4a676d..f1636c902b 100644 --- a/engines/cine/detection.cpp +++ b/engines/cine/detection.cpp @@ -84,7 +84,7 @@ public: _guiOptions = GUIO2(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVELOAD); } - virtual GameDescriptor findGame(const char *gameId) const { + PlainGameDescriptor findGame(const char *gameId) const override { return Engines::findGameID(gameId, _gameIds, obsoleteGameIDsTable); } 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..ee14acf7c8 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) { @@ -34,93 +35,182 @@ const PlainGameDescriptor *findPlainGameDescriptor(const char *gameid, const Pla return 0; } -GameDescriptor::GameDescriptor() { - setVal("gameid", ""); - setVal("description", ""); +PlainGameDescriptor PlainGameDescriptor::empty() { + PlainGameDescriptor pgd; + pgd.gameId = nullptr; + pgd.description = nullptr; + return pgd; } -GameDescriptor::GameDescriptor(const PlainGameDescriptor &pgd, Common::String guioptions) { - setVal("gameid", pgd.gameId); - setVal("description", pgd.description); +PlainGameDescriptor PlainGameDescriptor::of(const char *gameId, const char *description) { + PlainGameDescriptor pgd; + pgd.gameId = gameId; + pgd.description = description; + return pgd; +} - if (!guioptions.empty()) - setVal("guioptions", Common::getGameGUIOptionsDescription(guioptions)); +DetectedGame::DetectedGame() : + engineName(nullptr), + hasUnknownFiles(false), + canBeAdded(true), + language(Common::UNK_LANG), + platform(Common::kPlatformUnknown), + gameSupportLevel(kStableGame) { } -GameDescriptor::GameDescriptor(const Common::String &g, const Common::String &d, Common::Language l, Common::Platform p, Common::String guioptions, GameSupportLevel gsl) { - setVal("gameid", g); - setVal("description", d); - if (l != Common::UNK_LANG) - setVal("language", Common::getLanguageCode(l)); - if (p != Common::kPlatformUnknown) - setVal("platform", Common::getPlatformCode(p)); - if (!guioptions.empty()) - setVal("guioptions", Common::getGameGUIOptionsDescription(guioptions)); - - setSupportLevel(gsl); +DetectedGame::DetectedGame(const PlainGameDescriptor &pgd) : + engineName(nullptr), + hasUnknownFiles(false), + canBeAdded(true), + language(Common::UNK_LANG), + platform(Common::kPlatformUnknown), + gameSupportLevel(kStableGame) { + + gameId = pgd.gameId; + preferredTarget = pgd.gameId; + description = pgd.description; } -void GameDescriptor::setGUIOptions(Common::String guioptions) { - if (!guioptions.empty()) - setVal("guioptions", Common::getGameGUIOptionsDescription(guioptions)); - else - erase("guioptions"); +DetectedGame::DetectedGame(const Common::String &id, const Common::String &d, Common::Language l, Common::Platform p, const Common::String &ex) : + engineName(nullptr), + hasUnknownFiles(false), + canBeAdded(true), + gameSupportLevel(kStableGame) { + + gameId = id; + preferredTarget = id; + description = d; + language = l; + platform = p; + extra = ex; + + // Append additional information, if set, to the description. + description += updateDesc(); } -void GameDescriptor::appendGUIOptions(const Common::String &str) { - setVal("guioptions", getVal("guioptions", "") + " " + str); +void DetectedGame::setGUIOptions(const Common::String &guioptions) { + _guiOptions = Common::getGameGUIOptionsDescription(guioptions); } -void GameDescriptor::updateDesc(const char *extra) { - const bool hasCustomLanguage = (language() != Common::UNK_LANG); - const bool hasCustomPlatform = (platform() != Common::kPlatformUnknown); - const bool hasExtraDesc = (extra && extra[0]); +void DetectedGame::appendGUIOptions(const Common::String &str) { + if (!_guiOptions.empty()) + _guiOptions += " "; + + _guiOptions += str; +} + +Common::String DetectedGame::updateDesc() const { + const bool hasCustomLanguage = (language != Common::UNK_LANG); + const bool hasCustomPlatform = (platform != Common::kPlatformUnknown); + const bool hasExtraDesc = !extra.empty(); // Adapt the description string if custom platform/language is set. - if (hasCustomLanguage || hasCustomPlatform || hasExtraDesc) { - Common::String descr = description(); + Common::String descr; + if (!hasCustomLanguage && !hasCustomPlatform && !hasExtraDesc) + return descr; - descr += " ("; + descr += " ("; + + if (hasExtraDesc) + descr += extra; + if (hasCustomPlatform) { if (hasExtraDesc) - descr += extra; - if (hasCustomPlatform) { - if (hasExtraDesc) - descr += "/"; - descr += Common::getPlatformDescription(platform()); - } - if (hasCustomLanguage) { - if (hasExtraDesc || hasCustomPlatform) - descr += "/"; - descr += Common::getLanguageDescription(language()); + descr += "/"; + descr += Common::getPlatformDescription(platform); + } + if (hasCustomLanguage) { + if (hasExtraDesc || hasCustomPlatform) + descr += "/"; + descr += Common::getLanguageDescription(language); + } + + descr += ")"; + + return descr; +} + +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; } - descr += ")"; - setVal("description", descr); } + return false; } -GameSupportLevel GameDescriptor::getSupportLevel() { - GameSupportLevel gsl = kStableGame; - if (contains("gsl")) { - Common::String gslString = getVal("gsl"); - if (gslString.equals("unstable")) - gsl = kUnstableGame; - else if (gslString.equals("testing")) - gsl = kTestingGame; +DetectedGames DetectionResults::listRecognizedGames() { + DetectedGames candidates; + for (uint i = 0; i < _detectedGames.size(); i++) { + if (_detectedGames[i].canBeAdded) { + candidates.push_back(_detectedGames[i]); + } } - return gsl; + return candidates; } -void GameDescriptor::setSupportLevel(GameSupportLevel gsl) { - switch (gsl) { - case kUnstableGame: - setVal("gsl", "unstable"); - break; - case kTestingGame: - setVal("gsl", "testing"); - break; - case kStableGame: - // Fall Through intended - default: - erase("gsl"); +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].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.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..14f9962ce6 100644 --- a/engines/game.h +++ b/engines/game.h @@ -38,6 +38,9 @@ struct PlainGameDescriptor { const char *gameId; const char *description; + + static PlainGameDescriptor empty(); + static PlainGameDescriptor of(const char *gameId, const char *description); }; /** @@ -47,6 +50,18 @@ struct PlainGameDescriptor { */ const PlainGameDescriptor *findPlainGameDescriptor(const char *gameid, const PlainGameDescriptor *list); +class PlainGameList : public Common::Array<PlainGameDescriptor> { +public: + PlainGameList() {} + PlainGameList(const PlainGameList &list) : Common::Array<PlainGameDescriptor>(list) {} + PlainGameList(const PlainGameDescriptor *g) { + while (g->gameId) { + push_back(*g); + g++; + } + } +}; + /** * Ths is an enum to describe how done a game is. This also indicates what level of support is expected. */ @@ -56,63 +71,138 @@ enum GameSupportLevel { kUnstableGame // the game is not even ready for public testing yet }; + /** - * A hashmap describing details about a given game. In a sense this is a refined - * version of PlainGameDescriptor, as it also contains a gameid and a description string. - * But in addition, platform and language settings, as well as arbitrary other settings, - * can be contained in a GameDescriptor. - * This is an essential part of the glue between the game engines and the launcher code. + * A record describing the properties of a file. Used on the existing + * files while detecting a game. */ -class GameDescriptor : public Common::StringMap { -public: - GameDescriptor(); - GameDescriptor(const PlainGameDescriptor &pgd, Common::String guioptions = Common::String()); - GameDescriptor(const Common::String &gameid, - const Common::String &description, - Common::Language language = Common::UNK_LANG, - Common::Platform platform = Common::kPlatformUnknown, - Common::String guioptions = Common::String(), - GameSupportLevel gsl = kStableGame); +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; + +/** + * Details about a given game. + * + * While PlainGameDescriptor refers to a game supported by an engine, this refers to a game copy + * that has been detected by an engine's detector. + * It contains all the necessary data to add the game to the configuration manager and / or to launch it. + */ +struct DetectedGame { + DetectedGame(); + explicit DetectedGame(const PlainGameDescriptor &pgd); + DetectedGame(const Common::String &id, + const Common::String &description, + Common::Language language = Common::UNK_LANG, + Common::Platform platform = Common::kPlatformUnknown, + const Common::String &extra = Common::String()); + + void setGUIOptions(const Common::String &options); + void appendGUIOptions(const Common::String &str); + Common::String getGUIOptions() const { return _guiOptions; } /** - * Update the description string by appending (EXTRA/PLATFORM/LANG) to it. - * Values that are missing are omitted, so e.g. (EXTRA/LANG) would be - * added if no platform has been specified but a language and an extra string. + * The name of the engine supporting the detected game */ - void updateDesc(const char *extra = 0); + const char *engineName; - void setGUIOptions(Common::String options); - void appendGUIOptions(const Common::String &str); + /** + * 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; + + Common::String gameId; + Common::String preferredTarget; + Common::String description; + Common::Language language; + Common::Platform platform; + Common::String path; + Common::String extra; /** * What level of support is expected of this game */ - GameSupportLevel getSupportLevel(); - void setSupportLevel(GameSupportLevel gsl); - - Common::String &gameid() { return getVal("gameid"); } - Common::String &description() { return getVal("description"); } - const Common::String &gameid() const { return getVal("gameid"); } - const Common::String &description() const { return getVal("description"); } - Common::Language language() const { return contains("language") ? Common::parseLanguage(getVal("language")) : Common::UNK_LANG; } - Common::Platform platform() const { return contains("platform") ? Common::parsePlatform(getVal("platform")) : Common::kPlatformUnknown; } - - const Common::String &preferredtarget() const { - return contains("preferredtarget") ? getVal("preferredtarget") : getVal("gameid"); - } + GameSupportLevel gameSupportLevel; + +private: + /** + * Update the description string by appending (EXTRA/PLATFORM/LANG) to it. + * Values that are missing are omitted, so e.g. (EXTRA/LANG) would be + * added if no platform has been specified but a language and an extra string. + */ + Common::String updateDesc() const; + + Common::String _guiOptions; }; /** List of games. */ -class GameList : public Common::Array<GameDescriptor> { +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: - GameList() {} - GameList(const GameList &list) : Common::Array<GameDescriptor>(list) {} - GameList(const PlainGameDescriptor *g) { - while (g->gameId) { - push_back(GameDescriptor(*g)); - g++; - } - } + 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..864a701aa6 100644 --- a/engines/gob/detection/detection.cpp +++ b/engines/gob/detection/detection.cpp @@ -33,9 +33,9 @@ class GobMetaEngine : public AdvancedMetaEngine { public: GobMetaEngine(); - virtual GameDescriptor findGame(const char *gameId) const; + PlainGameDescriptor findGame(const char *gameId) const override; - 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; @@ -59,29 +59,26 @@ GobMetaEngine::GobMetaEngine() : _guiOptions = GUIO1(GUIO_NOLAUNCHLOAD); } -GameDescriptor GobMetaEngine::findGame(const char *gameId) const { +PlainGameDescriptor 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..a95ff1593e 100644 --- a/engines/metaengine.h +++ b/engines/metaengine.h @@ -69,17 +69,17 @@ public: virtual const char *getOriginalCopyright() const = 0; /** Returns a list of games supported by this engine. */ - virtual GameList getSupportedGames() const = 0; + virtual PlainGameList getSupportedGames() const = 0; - /** Query the engine for a GameDescriptor for the specified gameid, if any. */ - virtual GameDescriptor findGame(const char *gameid) const = 0; + /** Query the engine for a PlainGameDescriptor for the specified gameid, if any. */ + virtual PlainGameDescriptor findGame(const char *gameId) const = 0; /** * Runs the engine's game detector on the given list of files, and returns a * (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 @@ -267,10 +267,17 @@ public: */ 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; + PlainGameDescriptor findGameInLoadedPlugins(const Common::String &gameName, const Plugin **plugin = NULL) const; + PlainGameDescriptor findGame(const Common::String &gameName, const Plugin **plugin = NULL) const; + DetectionResults detectGames(const Common::FSList &fslist) const; const PluginList &getPlugins() const; + + /** + * Create a target from the supplied game descriptor + * + * Returns the created target name. + */ + Common::String createTargetForGame(const DetectedGame &game); }; /** Convenience shortcut for accessing the engine manager. */ diff --git a/engines/mohawk/detection.cpp b/engines/mohawk/detection.cpp index 9e177b7632..58d1483bee 100644 --- a/engines/mohawk/detection.cpp +++ b/engines/mohawk/detection.cpp @@ -177,7 +177,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/obsolete.cpp b/engines/obsolete.cpp index d65fb13ec1..ea96cff42e 100644 --- a/engines/obsolete.cpp +++ b/engines/obsolete.cpp @@ -55,7 +55,7 @@ void upgradeTargetIfNecessary(const ObsoleteGameID *obsoleteList) { } } -GameDescriptor findGameID( +PlainGameDescriptor findGameID( const char *gameid, const PlainGameDescriptor *gameids, const ObsoleteGameID *obsoleteList @@ -63,7 +63,7 @@ GameDescriptor findGameID( // First search the list of supported gameids for a match. const PlainGameDescriptor *g = findPlainGameDescriptor(gameid, gameids); if (g) - return GameDescriptor(*g); + return *g; // If we didn't find the gameid in the main list, check if it // is an obsolete game id. @@ -73,16 +73,16 @@ GameDescriptor findGameID( if (0 == scumm_stricmp(gameid, o->from)) { g = findPlainGameDescriptor(o->to, gameids); if (g && g->description) - return GameDescriptor(gameid, "Obsolete game ID (" + Common::String(g->description) + ")"); + return PlainGameDescriptor::of(gameid, g->description); else - return GameDescriptor(gameid, "Obsolete game ID"); + return PlainGameDescriptor::of(gameid, "Obsolete game ID"); } o++; } } // No match found - return GameDescriptor(); + return PlainGameDescriptor::empty(); } } // End of namespace Engines diff --git a/engines/obsolete.h b/engines/obsolete.h index be0963a7dc..7c7249e52b 100644 --- a/engines/obsolete.h +++ b/engines/obsolete.h @@ -66,7 +66,7 @@ void upgradeTargetIfNecessary(const ObsoleteGameID *obsoleteList); * Optionally can take a list of obsolete game ids into account in order * to support obsolete gameids. */ -GameDescriptor findGameID( +PlainGameDescriptor findGameID( const char *gameid, const PlainGameDescriptor *gameids, const ObsoleteGameID *obsoleteList = 0 diff --git a/engines/queen/detection.cpp b/engines/queen/detection.cpp index 0ad1b8322c..b9a7c5dff6 100644 --- a/engines/queen/detection.cpp +++ b/engines/queen/detection.cpp @@ -486,7 +486,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 { @@ -496,7 +496,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 @@ -531,11 +531,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/saga/detection.cpp b/engines/saga/detection.cpp index fcd78502d9..82c29d3389 100644 --- a/engines/saga/detection.cpp +++ b/engines/saga/detection.cpp @@ -105,7 +105,7 @@ public: _singleId = "saga"; } - virtual GameDescriptor findGame(const char *gameId) const { + PlainGameDescriptor findGame(const char *gameId) const override { return Engines::findGameID(gameId, _gameIds, obsoleteGameIDsTable); } 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..fccb30b0fa 100644 --- a/engines/scumm/detection.cpp +++ b/engines/scumm/detection.cpp @@ -959,9 +959,9 @@ public: virtual const char *getOriginalCopyright() const; 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; + PlainGameList getSupportedGames() const override; + PlainGameDescriptor findGame(const char *gameid) const override; + virtual DetectedGames detectGames(const Common::FSList &fslist) const override; virtual Common::Error createInstance(OSystem *syst, Engine **engine) const; @@ -992,11 +992,11 @@ bool ScummEngine::hasFeature(EngineFeature f) const { (f == kSupportsSubtitleOptions); } -GameList ScummMetaEngine::getSupportedGames() const { - return GameList(gameDescriptions); +PlainGameList ScummMetaEngine::getSupportedGames() const { + return PlainGameList(gameDescriptions); } -GameDescriptor ScummMetaEngine::findGame(const char *gameid) const { +PlainGameDescriptor ScummMetaEngine::findGame(const char *gameid) const { return Engines::findGameID(gameid, gameDescriptions, obsoleteGameIDsTable); } @@ -1026,29 +1026,26 @@ 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); - // Append additional information, if set, to the description. - dg.updateDesc(x->extra); + DetectedGame game = DetectedGame(x->game.gameid, g->description, x->language, x->game.platform, x->extra); // Compute and set the preferred target name for this game. // Based on generateComplexID() in advancedDetector.cpp. - dg["preferredtarget"] = generatePreferredTarget(*x); + game.preferredTarget = generatePreferredTarget(*x); - dg.setGUIOptions(x->game.guioptions + MidiDriver::musicType2GUIO(x->game.midi)); - dg.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(x->language)); + game.setGUIOptions(x->game.guioptions + MidiDriver::musicType2GUIO(x->game.midi)); + game.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..642e4d31a7 100644 --- a/engines/sky/detection.cpp +++ b/engines/sky/detection.cpp @@ -76,10 +76,10 @@ public: virtual const char *getOriginalCopyright() const; virtual bool hasFeature(MetaEngineFeature f) const; - virtual GameList getSupportedGames() const; + PlainGameList getSupportedGames() const override; 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; + PlainGameDescriptor findGame(const char *gameid) const override; + DetectedGames detectGames(const Common::FSList &fslist) const override; virtual Common::Error createInstance(OSystem *syst, Engine **engine) const; @@ -110,8 +110,8 @@ bool Sky::SkyEngine::hasFeature(EngineFeature f) const { (f == kSupportsSavingDuringRuntime); } -GameList SkyMetaEngine::getSupportedGames() const { - GameList games; +PlainGameList SkyMetaEngine::getSupportedGames() const { + PlainGameList games; games.push_back(skySetting); return games; } @@ -135,14 +135,14 @@ const ExtraGuiOptions SkyMetaEngine::getExtraGuiOptions(const Common::String &ta return options; } -GameDescriptor SkyMetaEngine::findGame(const char *gameid) const { +PlainGameDescriptor SkyMetaEngine::findGame(const char *gameid) const { if (0 == scumm_stricmp(gameid, skySetting.gameId)) return skySetting; - return GameDescriptor(); + return PlainGameDescriptor::empty(); } -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,25 @@ 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); const SkyVersion *sv = skyVersions; while (sv->dinnerTableEntries) { if (dinnerTableEntries == sv->dinnerTableEntries && (sv->dataDiskSize == dataDiskSize || sv->dataDiskSize == -1)) { - dg.updateDesc(Common::String::format("v0.0%d %s", sv->version, sv->extraDesc).c_str()); - dg.setGUIOptions(sv->guioptions); break; } ++sv; } - detectedGames.push_back(dg); + + if (sv->dinnerTableEntries) { + Common::String extra = Common::String::format("v0.0%d %s", sv->version, sv->extraDesc); + + DetectedGame game = DetectedGame(skySetting.gameId, skySetting.description, Common::UNK_LANG, Common::kPlatformUnknown, extra); + game.setGUIOptions(sv->guioptions); + + detectedGames.push_back(game); + } else { + detectedGames.push_back(DetectedGame(skySetting.gameId, skySetting.description)); + } } return detectedGames; diff --git a/engines/sludge/detection.cpp b/engines/sludge/detection.cpp index 361d44b1e1..8c5c0ac13d 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..52394cec41 100644 --- a/engines/sword1/detection.cpp +++ b/engines/sword1/detection.cpp @@ -87,9 +87,9 @@ 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; + PlainGameList getSupportedGames() const override; + PlainGameDescriptor findGame(const char *gameId) const override; + 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; @@ -116,31 +116,31 @@ bool Sword1::SwordEngine::hasFeature(EngineFeature f) const { (f == kSupportsLoadingDuringRuntime); } -GameList SwordMetaEngine::getSupportedGames() const { - GameList games; - games.push_back(GameDescriptor(sword1FullSettings, GUIO_NOMIDI)); - games.push_back(GameDescriptor(sword1DemoSettings, GUIO_NOMIDI)); - games.push_back(GameDescriptor(sword1MacFullSettings, GUIO_NOMIDI)); - games.push_back(GameDescriptor(sword1MacDemoSettings, GUIO_NOMIDI)); - games.push_back(GameDescriptor(sword1PSXSettings, GUIO_NOMIDI)); - games.push_back(GameDescriptor(sword1PSXDemoSettings, GUIO_NOMIDI)); +PlainGameList SwordMetaEngine::getSupportedGames() const { + PlainGameList games; + games.push_back(sword1FullSettings); + games.push_back(sword1DemoSettings); + games.push_back(sword1MacFullSettings); + games.push_back(sword1MacDemoSettings); + games.push_back(sword1PSXSettings); + games.push_back(sword1PSXDemoSettings); return games; } -GameDescriptor SwordMetaEngine::findGame(const char *gameid) const { - if (0 == scumm_stricmp(gameid, sword1FullSettings.gameId)) +PlainGameDescriptor SwordMetaEngine::findGame(const char *gameId) const { + if (0 == scumm_stricmp(gameId, sword1FullSettings.gameId)) return sword1FullSettings; - if (0 == scumm_stricmp(gameid, sword1DemoSettings.gameId)) + if (0 == scumm_stricmp(gameId, sword1DemoSettings.gameId)) return sword1DemoSettings; - if (0 == scumm_stricmp(gameid, sword1MacFullSettings.gameId)) + if (0 == scumm_stricmp(gameId, sword1MacFullSettings.gameId)) return sword1MacFullSettings; - if (0 == scumm_stricmp(gameid, sword1MacDemoSettings.gameId)) + if (0 == scumm_stricmp(gameId, sword1MacDemoSettings.gameId)) return sword1MacDemoSettings; - if (0 == scumm_stricmp(gameid, sword1PSXSettings.gameId)) + if (0 == scumm_stricmp(gameId, sword1PSXSettings.gameId)) return sword1PSXSettings; - if (0 == scumm_stricmp(gameid, sword1PSXDemoSettings.gameId)) + if (0 == scumm_stricmp(gameId, sword1PSXDemoSettings.gameId)) return sword1PSXDemoSettings; - return GameDescriptor(); + return PlainGameDescriptor::empty(); } void Sword1CheckDirectory(const Common::FSList &fslist, bool *filesFound, bool recursion = false) { @@ -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,33 @@ 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 = DetectedGame(sword1DemoSettings); else if (mainFilesFound && pcFilesFound && psxFilesFound) - gd = GameDescriptor(sword1PSXSettings, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); + game = DetectedGame(sword1PSXSettings); else if (mainFilesFound && pcFilesFound && psxDemoFilesFound) - gd = GameDescriptor(sword1PSXDemoSettings, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); + game = DetectedGame(sword1PSXDemoSettings); else if (mainFilesFound && pcFilesFound && !psxFilesFound) - gd = GameDescriptor(sword1FullSettings, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); + game = DetectedGame(sword1FullSettings); else if (mainFilesFound && macFilesFound) - gd = GameDescriptor(sword1MacFullSettings, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); + game = DetectedGame(sword1MacFullSettings); else if (mainFilesFound && macDemoFilesFound) - gd = GameDescriptor(sword1MacDemoSettings, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); + game = DetectedGame(sword1MacDemoSettings); 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.setGUIOptions(GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)); - detectedGames.push_back(gd); + game.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::EN_ANY)); + game.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::DE_DEU)); + game.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::FR_FRA)); + game.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::IT_ITA)); + game.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::ES_ESP)); + game.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::PT_BRA)); + game.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::CZ_CZE)); + + detectedGames.push_back(game); return detectedGames; } diff --git a/engines/sword2/sword2.cpp b/engines/sword2/sword2.cpp index 10ddda7d2e..4d8399e630 100644 --- a/engines/sword2/sword2.cpp +++ b/engines/sword2/sword2.cpp @@ -92,10 +92,10 @@ public: } virtual bool hasFeature(MetaEngineFeature f) const; - virtual GameList getSupportedGames() const; + PlainGameList getSupportedGames() const override; 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; + PlainGameDescriptor findGame(const char *gameid) const override; + 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; @@ -119,11 +119,11 @@ bool Sword2::Sword2Engine::hasFeature(EngineFeature f) const { (f == kSupportsLoadingDuringRuntime); } -GameList Sword2MetaEngine::getSupportedGames() const { +PlainGameList Sword2MetaEngine::getSupportedGames() const { const Sword2::GameSettings *g = Sword2::sword2_settings; - GameList games; + PlainGameList games; while (g->gameid) { - games.push_back(GameDescriptor(g->gameid, g->description)); + games.push_back(PlainGameDescriptor::of(g->gameid, g->description)); g++; } return games; @@ -135,20 +135,20 @@ const ExtraGuiOptions Sword2MetaEngine::getExtraGuiOptions(const Common::String return options; } -GameDescriptor Sword2MetaEngine::findGame(const char *gameid) const { +PlainGameDescriptor Sword2MetaEngine::findGame(const char *gameid) const { const Sword2::GameSettings *g = Sword2::sword2_settings; while (g->gameid) { if (0 == scumm_stricmp(gameid, g->gameid)) break; g++; } - return GameDescriptor(g->gameid, g->description); + return PlainGameDescriptor::of(g->gameid, g->description); } 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 = DetectedGame(g->gameid, g->description); + game.setGUIOptions(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].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/EventRecorder.cpp b/gui/EventRecorder.cpp index 849405410a..560df0ec35 100644 --- a/gui/EventRecorder.cpp +++ b/gui/EventRecorder.cpp @@ -600,13 +600,13 @@ void EventRecorder::setFileHeader() { return; } TimeDate t; - GameDescriptor desc = EngineMan.findGame(ConfMan.getActiveDomainName()); + PlainGameDescriptor desc = EngineMan.findGame(ConfMan.getActiveDomainName()); g_system->getTimeAndDate(t); if (_author.empty()) { setAuthor("Unknown Author"); } if (_name.empty()) { - g_eventRec.setName(Common::String::format("%.2d.%.2d.%.4d ", t.tm_mday, t.tm_mon, 1900 + t.tm_year) + desc.description()); + g_eventRec.setName(Common::String::format("%.2d.%.2d.%.4d ", t.tm_mday, t.tm_mon, 1900 + t.tm_year) + desc.description); } _playbackFile->getHeader().author = _author; _playbackFile->getHeader().notes = _desc; diff --git a/gui/editgamedialog.cpp b/gui/editgamedialog.cpp index fa0d3fac9b..80fb41fad1 100644 --- a/gui/editgamedialog.cpp +++ b/gui/editgamedialog.cpp @@ -95,7 +95,7 @@ protected: } }; -EditGameDialog::EditGameDialog(const String &domain, const String &desc) +EditGameDialog::EditGameDialog(const String &domain) : OptionsDialog(domain, "GameOptions") { // Retrieve all game specific options. const Plugin *plugin = nullptr; @@ -106,7 +106,7 @@ EditGameDialog::EditGameDialog(const String &domain, const String &desc) gameId = domain; // Retrieve the plugin, since we need to access the engine's MetaEngine // implementation. - EngineMan.findGame(gameId, &plugin); + PlainGameDescriptor pgd = EngineMan.findGame(gameId, &plugin); if (plugin) { _engineOptions = plugin->get<MetaEngine>().getExtraGuiOptions(domain); } else { @@ -120,8 +120,8 @@ EditGameDialog::EditGameDialog(const String &domain, const String &desc) // GAME: Determine the description string String description(ConfMan.get("description", domain)); - if (description.empty() && !desc.empty()) { - description = desc; + if (description.empty() && pgd.description) { + description = pgd.description; } // GUI: Add tab widget diff --git a/gui/editgamedialog.h b/gui/editgamedialog.h index a317e364c6..7c6a08eb3c 100644 --- a/gui/editgamedialog.h +++ b/gui/editgamedialog.h @@ -40,8 +40,6 @@ class StaticTextWidget; class EditTextWidget; class SaveLoadChooser; -Common::String addGameToConf(const GameDescriptor &result); - /* * A dialog that allows the user to edit a config game entry. * TODO: add widgets for some/all of the following @@ -62,7 +60,7 @@ class EditGameDialog : public OptionsDialog { typedef Common::String String; typedef Common::Array<Common::String> StringArray; public: - EditGameDialog(const String &domain, const String &desc); + EditGameDialog(const String &domain); void open(); virtual void apply(); diff --git a/gui/launcher.cpp b/gui/launcher.cpp index 857d7d001a..7a37a7d3be 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" @@ -266,9 +267,9 @@ void LauncherDialog::updateListing() { if (gameid.empty()) gameid = iter->_key; if (description.empty()) { - GameDescriptor g = EngineMan.findGame(gameid); - if (g.contains("description")) - description = g.description(); + PlainGameDescriptor g = EngineMan.findGame(gameid); + if (g.description) + description = g.description; } if (description.empty()) { @@ -375,45 +376,6 @@ void LauncherDialog::addGame() { } while (looping); } -Common::String addGameToConf(const GameDescriptor &result) { - // The auto detector or the user made a choice. - // Pick a domain name which does not yet exist (after all, we - // are *adding* a game to the config, not replacing). - Common::String domain = result.preferredtarget(); - - assert(!domain.empty()); - if (ConfMan.hasGameDomain(domain)) { - int suffixN = 1; - Common::String gameid(domain); - - while (ConfMan.hasGameDomain(domain)) { - domain = gameid + Common::String::format("-%d", suffixN); - suffixN++; - } - } - - // Add the name domain - ConfMan.addGameDomain(domain); - - // Copy all non-empty key/value pairs into the new domain - for (GameDescriptor::const_iterator iter = result.begin(); iter != result.end(); ++iter) { - if (!iter->_value.empty() && iter->_key != "preferredtarget") - ConfMan.set(iter->_key, iter->_value, domain); - } - - // TODO: Setting the description field here has the drawback - // that the user does never notice when we upgrade our descriptions. - // It might be nice ot leave this field empty, and only set it to - // a value when the user edits the description string. - // However, at this point, that's impractical. Once we have a method - // to query all backends for the proper & full description of a given - // game target, we can change this (currently, you can only query - // for the generic gameid description; it's not possible to obtain - // a description which contains extended information like language, etc.). - - return domain; -} - void LauncherDialog::removeGame(int item) { MessageDialog alert(_("Do you really want to remove this game configuration?"), _("Yes"), _("No")); @@ -442,7 +404,8 @@ void LauncherDialog::editGame(int item) { String gameId(ConfMan.get("gameid", _domains[item])); if (gameId.empty()) gameId = _domains[item]; - EditGameDialog editDialog(_domains[item], EngineMan.findGame(gameId).description()); + + EditGameDialog editDialog(_domains[item]); if (editDialog.runModal() > 0) { // User pressed OK, so make changes permanent @@ -573,7 +536,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,22 +562,19 @@ 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].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 DetectedGame &result = candidates[idx]; - Common::String domain = addGameToConf(result); + Common::String domain = EngineMan.createTargetForGame(result); // Display edit dialog for the new entry - EditGameDialog editDialog(domain, result.description()); + EditGameDialog editDialog(domain); if (editDialog.runModal() > 0) { // User pressed OK, so make changes permanent diff --git a/gui/launcher.h b/gui/launcher.h index 08413fe3d2..9f0a1c8e95 100644 --- a/gui/launcher.h +++ b/gui/launcher.h @@ -38,8 +38,6 @@ class StaticTextWidget; class EditTextWidget; class SaveLoadChooser; -Common::String addGameToConf(const GameDescriptor &result); - class LauncherDialog : public Dialog { typedef Common::String String; typedef Common::Array<Common::String> StringArray; diff --git a/gui/massadd.cpp b/gui/massadd.cpp index db569fac82..8bc5a10720 100644 --- a/gui/massadd.cpp +++ b/gui/massadd.cpp @@ -28,10 +28,7 @@ #include "common/taskbar.h" #include "common/translation.h" -#include "gui/launcher.h" // For addGameToConf() #include "gui/massadd.h" -#include "gui/widget.h" -#include "gui/widgets/list.h" #ifndef DISABLE_MASS_ADD namespace GUI { @@ -120,14 +117,14 @@ MassAddDialog::MassAddDialog(const Common::FSNode &startDir) } struct GameTargetLess { - bool operator()(const GameDescriptor &x, const GameDescriptor &y) const { - return x.preferredtarget().compareToIgnoreCase(y.preferredtarget()) < 0; + bool operator()(const DetectedGame &x, const DetectedGame &y) const { + return x.preferredTarget.compareToIgnoreCase(y.preferredTarget) < 0; } }; struct GameDescLess { - bool operator()(const GameDescriptor &x, const GameDescriptor &y) const { - return x.description().compareToIgnoreCase(y.description()) < 0; + bool operator()(const DetectedGame &x, const DetectedGame &y) const { + return x.description.compareToIgnoreCase(y.description) < 0; } }; @@ -143,13 +140,13 @@ void MassAddDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data if (cmd == kOkCmd) { // Sort the detected games. This is not strictly necessary, but nice for // people who want to edit their config file by hand after a mass add. - sort(_games.begin(), _games.end(), GameTargetLess()); + Common::sort(_games.begin(), _games.end(), GameTargetLess()); // Add all the detected games to the config - for (GameList::iterator iter = _games.begin(); iter != _games.end(); ++iter) { + for (DetectedGames::iterator iter = _games.begin(); iter != _games.end(); ++iter) { debug(1, " Added gameid '%s', desc '%s'\n", - (*iter)["gameid"].c_str(), - (*iter)["description"].c_str()); - (*iter)["gameid"] = addGameToConf(*iter); + iter->gameId.c_str(), + iter->description.c_str()); + iter->gameId = EngineMan.createTargetForGame(*iter); } // Write everything to disk @@ -157,8 +154,8 @@ void MassAddDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data // And scroll to first detected game if (!_games.empty()) { - sort(_games.begin(), _games.end(), GameDescLess()); - ConfMan.set("temp_selection", _games.front().gameid()); + Common::sort(_games.begin(), _games.end(), GameDescLess()); + ConfMan.set("temp_selection", _games.front().gameId); } close(); @@ -187,7 +184,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 +197,10 @@ 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 DetectedGame &result = *cand; + Common::String path = dir.getPath(); // Remove trailing slashes @@ -205,6 +209,9 @@ void MassAddDialog::handleTickle() { // Check for existing config entries for this path/gameid/lang/platform combination if (_pathToTargets.contains(path)) { + const char *resultPlatformCode = Common::getPlatformCode(result.platform); + const char *resultLanguageCode = Common::getLanguageCode(result.language); + bool duplicate = false; const StringArray &targets = _pathToTargets[path]; for (StringArray::const_iterator iter = targets.begin(); iter != targets.end(); ++iter) { @@ -212,9 +219,9 @@ void MassAddDialog::handleTickle() { Common::ConfigManager::Domain *dom = ConfMan.getDomain(*iter); assert(dom); - if ((*dom)["gameid"] == result["gameid"] && - (*dom)["platform"] == result["platform"] && - (*dom)["language"] == result["language"]) { + if ((*dom)["gameid"] == result.gameId && + (*dom)["platform"] == resultPlatformCode && + (*dom)["language"] == resultLanguageCode) { duplicate = true; break; } @@ -224,10 +231,9 @@ void MassAddDialog::handleTickle() { break; // Skip duplicates } } - result["path"] = path; _games.push_back(result); - _list->append(result.description()); + _list->append(result.description); } diff --git a/gui/massadd.h b/gui/massadd.h index 116a420d79..b81a6046e2 100644 --- a/gui/massadd.h +++ b/gui/massadd.h @@ -24,6 +24,7 @@ #define MASSADD_DIALOG_H #include "gui/dialog.h" +#include "gui/widgets/list.h" #include "common/fs.h" #include "common/hashmap.h" #include "common/stack.h" @@ -44,13 +45,13 @@ public: Common::String getFirstAddedTarget() const { if (!_games.empty()) - return _games.front().gameid(); + return _games.front().gameId; return Common::String(); } private: Common::Stack<Common::FSNode> _scanStack; - GameList _games; + DetectedGames _games; /** * Map each path occuring in the config file to the target(s) using that path. diff --git a/gui/recorderdialog.cpp b/gui/recorderdialog.cpp index cd89b58f00..7a2cd048f4 100644 --- a/gui/recorderdialog.cpp +++ b/gui/recorderdialog.cpp @@ -167,9 +167,9 @@ void RecorderDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat case kRecordCmd: { TimeDate t; Common::String gameId = ConfMan.get("gameid", _target); - GameDescriptor desc = EngineMan.findGame(gameId); + PlainGameDescriptor desc = EngineMan.findGame(gameId); g_system->getTimeAndDate(t); - EditRecordDialog editDlg(_("Unknown Author"), Common::String::format("%.2d.%.2d.%.4d ", t.tm_mday, t.tm_mon, 1900 + t.tm_year) + desc.description(), ""); + EditRecordDialog editDlg(_("Unknown Author"), Common::String::format("%.2d.%.2d.%.4d ", t.tm_mday, t.tm_mon, 1900 + t.tm_year) + desc.description, ""); if (editDlg.runModal() != kOKCmd) { return; } |