Merge pull request #1187 from bgK/detection-refactor-unknown

ENGINES: Return unknown game variants with the list of detected games
This commit is contained in:
Bastien Bouclet 2018-05-28 18:43:15 +02:00 committed by GitHub
commit 61f9398b04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 964 additions and 840 deletions

View File

@ -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,

View File

@ -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;
}
DetectionResults detectionResults = EngineMan.detectGames(files);
static bool addGameToConf(const GameDescriptor &gd) {
const Common::String &domain = gd.preferredtarget();
// 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

View File

@ -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

View File

@ -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

View File

@ -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);
}
}

View File

@ -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;
static Common::String sanitizeName(const char *name) {
Common::String res;
if (g.flags & ADGF_USEEXTRAASTITLE) {
title = g.extra;
extra = "";
} else {
while (sg->gameId) {
if (!scumm_stricmp(g.gameId, sg->gameId))
title = sg->description;
sg++;
}
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;
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;
}
return res;
}
DetectedGame game(gameId, title, desc->language, desc->platform, extra);
game.hasUnknownFiles = adGame.hasUnknownFiles;
game.matchedFiles = adGame.matchedFiles;
game.preferredTarget = generatePreferredTarget(desc);
void AdvancedMetaEngine::updateGameDescriptor(GameDescriptor &desc, const ADGameDescription *realDesc) const {
if (_singleId != NULL) {
desc["preferredtarget"] = desc["gameid"];
desc["gameid"] = _singleId;
}
game.gameSupportLevel = kStableGame;
if (desc->flags & ADGF_UNSTABLE)
game.gameSupportLevel = kUnstableGame;
else if (desc->flags & ADGF_TESTING)
game.gameSupportLevel = kTestingGame;
if (!desc.contains("preferredtarget"))
desc["preferredtarget"] = desc["gameid"];
game.setGUIOptions(desc->guiOptions + _guiOptions);
game.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(desc->language));
if (realDesc->flags & ADGF_AUTOGENTARGET) {
if (*realDesc->extra)
desc["preferredtarget"] = sanitizeName(realDesc->extra);
}
desc["preferredtarget"] = generatePreferredTarget(desc["preferredtarget"], realDesc);
if (desc->flags & ADGF_ADDENGLISH)
game.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::EN_ANY));
if (_flags & kADFlagUseExtraAsHint)
desc["extra"] = realDesc->extra;
game.extra = desc->extra;
desc.setGUIOptions(realDesc->guiOptions + _guiOptions);
desc.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(realDesc->language));
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;
if (getFileProperties(fslist.begin()->getParent(), allFiles, *agdesc, *filenames, tmp))
(*filesProps)[*filenames] = tmp;
}
for (filenames = ptr->filenames; *filenames; ++filenames) {
FileProperties 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)

View File

@ -47,20 +47,6 @@ struct ADGameFileDescription {
int32 fileSize; ///< Size of the described file. Set to -1 to ignore.
};
/**
* 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

View File

@ -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)

View File

@ -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);
}

View File

@ -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 {

View File

@ -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;
ADDetectedGame CGE2MetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const {
ADDetectedGame game = detectGameFilebased(allFiles, fslist, CGE2::fileBasedFallback);
const ADGameDescription *game;
game = detectGameFilebased(allFiles, fslist, CGE2::fileBasedFallback, &filesProps);
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 {

View File

@ -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);
}

View File

@ -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)

View File

@ -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);
if (!guioptions.empty())
setVal("guioptions", Common::getGameGUIOptionsDescription(guioptions));
PlainGameDescriptor PlainGameDescriptor::of(const char *gameId, const char *description) {
PlainGameDescriptor pgd;
pgd.gameId = gameId;
pgd.description = description;
return pgd;
}
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() :
engineName(nullptr),
hasUnknownFiles(false),
canBeAdded(true),
language(Common::UNK_LANG),
platform(Common::kPlatformUnknown),
gameSupportLevel(kStableGame) {
}
void GameDescriptor::setGUIOptions(Common::String guioptions) {
if (!guioptions.empty())
setVal("guioptions", Common::getGameGUIOptionsDescription(guioptions));
else
erase("guioptions");
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::appendGUIOptions(const Common::String &str) {
setVal("guioptions", getVal("guioptions", "") + " " + str);
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::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::setGUIOptions(const Common::String &guioptions) {
_guiOptions = Common::getGameGUIOptionsDescription(guioptions);
}
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 += ")";
setVal("description", descr);
descr += "/";
descr += Common::getPlatformDescription(platform);
}
if (hasCustomLanguage) {
if (hasExtraDesc || hasCustomPlatform)
descr += "/";
descr += Common::getLanguageDescription(language);
}
descr += ")";
return descr;
}
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;
}
return gsl;
DetectionResults::DetectionResults(const DetectedGames &detectedGames) :
_detectedGames(detectedGames) {
}
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");
bool DetectionResults::foundUnknownGames() const {
for (uint i = 0; i < _detectedGames.size(); i++) {
if (_detectedGames[i].hasUnknownFiles) {
return true;
}
}
return false;
}
DetectedGames DetectionResults::listRecognizedGames() {
DetectedGames candidates;
for (uint i = 0; i < _detectedGames.size(); i++) {
if (_detectedGames[i].canBeAdded) {
candidates.push_back(_detectedGames[i]);
}
}
return candidates;
}
Common::String DetectionResults::generateUnknownGameReport(bool translate, uint32 wordwrapAt) const {
assert(!_detectedGames.empty());
const char *reportStart = _s("The game in '%s' seems to be an unknown game variant.\n\n"
"Please report the following data to the ScummVM team at %s "
"along with the name of the game you tried to add and "
"its version, language, etc.:");
const char *reportEngineHeader = _s("Matched game IDs for the %s engine:");
Common::String report = Common::String::format(
translate ? _(reportStart) : reportStart, _detectedGames[0].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;
}

View File

@ -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.
*/
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);
/**
* A record describing the properties of a file. Used on the existing
* files while detecting a game.
*/
struct FileProperties {
int32 size;
Common::String md5;
FileProperties() : size(-1) {}
};
/**
* A map of all relevant existing files while detecting.
*/
typedef Common::HashMap<Common::String, FileProperties, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> FilePropertiesMap;
/**
* 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; }
/**
* The name of the engine supporting the detected game
*/
const char *engineName;
/**
* 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 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.
*/
void updateDesc(const char *extra = 0);
Common::String updateDesc() const;
void setGUIOptions(Common::String options);
void appendGUIOptions(const Common::String &str);
/**
* 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");
}
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

View File

@ -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) {

View File

@ -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)

View File

@ -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. */

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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);
}

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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; }

View File

@ -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);
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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) {
UnknownGameDialog::UnknownGameDialog(const DetectionResults &detectionResults) :
Dialog(30, 20, 260, 124),
_detectionResults(detectionResults) {
Common::String reportTranslated = _detectionResults.generateUnknownGameReport(true);
_reportData = reportData;
_reportTranslated = reportTranslated;
_bugtrackerAffectedEngine = bugtrackerAffectedEngine;
//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:

View File

@ -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;
};

View File

@ -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 {

View File

@ -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;

View File

@ -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

View File

@ -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();

View File

@ -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];
const DetectedGame &result = candidates[idx];
// TODO: Change the detectors to set "path" !
result["path"] = dir.getPath();
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

View File

@ -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;

View File

@ -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);
}

View File

@ -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.

View File

@ -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;
}