ENGINES: Add unknown game variants to the game detector results
This commit is contained in:
parent
9587dd5c21
commit
cf1ebf2951
31 changed files with 597 additions and 428 deletions
|
@ -30,8 +30,6 @@
|
|||
#include "common/textconsole.h"
|
||||
#include "common/translation.h"
|
||||
#include "gui/EventRecorder.h"
|
||||
#include "gui/gui-manager.h"
|
||||
#include "engines/unknown-game-dialog.h"
|
||||
#include "engines/advancedDetector.h"
|
||||
#include "engines/obsolete.h"
|
||||
|
||||
|
@ -63,6 +61,17 @@ static GameDescriptor toGameDescriptor(const ADGameDescription &g, const PlainGa
|
|||
return gd;
|
||||
}
|
||||
|
||||
DetectedGame AdvancedMetaEngine::toDetectedGame(const ADDetectedGame &adGame) const {
|
||||
DetectedGame game;
|
||||
game.engineName = getName();
|
||||
game.gameId = adGame.desc->gameId;
|
||||
game.hasUnknownFiles = adGame.hasUnknownFiles;
|
||||
game.matchedFiles = adGame.matchedFiles;
|
||||
game.matchedGame = toGameDescriptor(*adGame.desc, _gameIds);
|
||||
updateGameDescriptor(game.matchedGame, adGame.desc);
|
||||
return game;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a preferred target value as
|
||||
* GAMEID-PLAFORM-LANG
|
||||
|
@ -129,11 +138,11 @@ void AdvancedMetaEngine::updateGameDescriptor(GameDescriptor &desc, const ADGame
|
|||
desc.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::EN_ANY));
|
||||
}
|
||||
|
||||
bool cleanupPirated(ADGameDescList &matched) {
|
||||
bool cleanupPirated(ADDetectedGames &matched) {
|
||||
// OKay, now let's sense presence of pirated games
|
||||
if (!matched.empty()) {
|
||||
for (uint j = 0; j < matched.size();) {
|
||||
if (matched[j]->flags & ADGF_PIRATED)
|
||||
if (matched[j].desc->flags & ADGF_PIRATED)
|
||||
matched.remove_at(j);
|
||||
else
|
||||
++j;
|
||||
|
@ -150,35 +159,46 @@ bool cleanupPirated(ADGameDescList &matched) {
|
|||
}
|
||||
|
||||
|
||||
GameList AdvancedMetaEngine::detectGames(const Common::FSList &fslist, bool useUnknownGameDialog) const {
|
||||
ADGameDescList matches;
|
||||
GameList detectedGames;
|
||||
DetectedGames AdvancedMetaEngine::detectGames(const Common::FSList &fslist) const {
|
||||
FileMap allFiles;
|
||||
|
||||
if (fslist.empty())
|
||||
return detectedGames;
|
||||
return DetectedGames();
|
||||
|
||||
// Compose a hashmap of all files in fslist.
|
||||
composeFileHashMap(allFiles, fslist, (_maxScanDepth == 0 ? 1 : _maxScanDepth));
|
||||
|
||||
// Run the detector on this
|
||||
matches = detectGame(fslist.begin()->getParent(), allFiles, Common::UNK_LANG, Common::kPlatformUnknown, "", useUnknownGameDialog);
|
||||
ADDetectedGames matches = detectGame(fslist.begin()->getParent(), allFiles, Common::UNK_LANG, Common::kPlatformUnknown, "");
|
||||
|
||||
if (matches.empty()) {
|
||||
// Use fallback detector if there were no matches by other means
|
||||
const ADGameDescription *fallbackDesc = fallbackDetect(allFiles, fslist);
|
||||
if (fallbackDesc != nullptr) {
|
||||
GameDescriptor desc(toGameDescriptor(*fallbackDesc, _gameIds));
|
||||
updateGameDescriptor(desc, fallbackDesc);
|
||||
detectedGames.push_back(desc);
|
||||
cleanupPirated(matches);
|
||||
|
||||
DetectedGames detectedGames;
|
||||
for (uint i = 0; i < matches.size(); i++) {
|
||||
DetectedGame game = toDetectedGame(matches[i]);
|
||||
|
||||
if (game.hasUnknownFiles) {
|
||||
// Non fallback games with unknown files cannot be added/launched
|
||||
game.canBeAdded = false;
|
||||
}
|
||||
} else {
|
||||
// Otherwise use the found matches
|
||||
cleanupPirated(matches);
|
||||
for (uint i = 0; i < matches.size(); i++) {
|
||||
GameDescriptor desc(toGameDescriptor(*matches[i], _gameIds));
|
||||
updateGameDescriptor(desc, matches[i]);
|
||||
detectedGames.push_back(desc);
|
||||
|
||||
detectedGames.push_back(game);
|
||||
}
|
||||
|
||||
bool foundKnownGames = false;
|
||||
for (uint i = 0; i < detectedGames.size(); i++) {
|
||||
foundKnownGames |= detectedGames[i].canBeAdded;
|
||||
}
|
||||
|
||||
if (!foundKnownGames) {
|
||||
// Use fallback detector if there were no matches by other means
|
||||
ADDetectedGame fallbackDetectionResult = fallbackDetect(allFiles, fslist);
|
||||
|
||||
if (fallbackDetectionResult.desc) {
|
||||
DetectedGame fallbackDetectedGame = toDetectedGame(fallbackDetectionResult);
|
||||
fallbackDetectedGame.preferredTarget += "-fallback";
|
||||
|
||||
detectedGames.push_back(fallbackDetectedGame);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,7 +236,6 @@ const ExtraGuiOptions AdvancedMetaEngine::getExtraGuiOptions(const Common::Strin
|
|||
Common::Error AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) const {
|
||||
assert(engine);
|
||||
|
||||
const ADGameDescription *agdDesc = 0;
|
||||
Common::Language language = Common::UNK_LANG;
|
||||
Common::Platform platform = Common::kPlatformUnknown;
|
||||
Common::String extra;
|
||||
|
@ -266,46 +285,43 @@ Common::Error AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine)
|
|||
composeFileHashMap(allFiles, files, (_maxScanDepth == 0 ? 1 : _maxScanDepth));
|
||||
|
||||
// Run the detector on this
|
||||
ADGameDescList matches = detectGame(files.begin()->getParent(), allFiles, language, platform, extra);
|
||||
ADDetectedGames matches = detectGame(files.begin()->getParent(), allFiles, language, platform, extra);
|
||||
|
||||
if (cleanupPirated(matches))
|
||||
return Common::kNoGameDataFoundError;
|
||||
|
||||
if (_singleId == NULL) {
|
||||
// Find the first match with correct gameid.
|
||||
for (uint i = 0; i < matches.size(); i++) {
|
||||
if (matches[i]->gameId == gameid) {
|
||||
agdDesc = matches[i];
|
||||
break;
|
||||
}
|
||||
ADDetectedGame agdDesc;
|
||||
for (uint i = 0; i < matches.size(); i++) {
|
||||
if ((_singleId || matches[i].desc->gameId == gameid) && !matches[i].hasUnknownFiles) {
|
||||
agdDesc = matches[i];
|
||||
break;
|
||||
}
|
||||
} else if (matches.size() > 0) {
|
||||
agdDesc = matches[0];
|
||||
}
|
||||
|
||||
if (agdDesc == 0) {
|
||||
if (!agdDesc.desc) {
|
||||
// Use fallback detector if there were no matches by other means
|
||||
agdDesc = fallbackDetect(allFiles, files);
|
||||
if (agdDesc != 0) {
|
||||
ADDetectedGame fallbackDetectedGame = fallbackDetect(allFiles, files);
|
||||
agdDesc = fallbackDetectedGame;
|
||||
if (agdDesc.desc) {
|
||||
// Seems we found a fallback match. But first perform a basic
|
||||
// sanity check: the gameid must match.
|
||||
if (_singleId == NULL && agdDesc->gameId != gameid)
|
||||
agdDesc = 0;
|
||||
if (!_singleId && agdDesc.desc->gameId != gameid)
|
||||
agdDesc = ADDetectedGame();
|
||||
}
|
||||
}
|
||||
|
||||
if (agdDesc == 0)
|
||||
if (!agdDesc.desc)
|
||||
return Common::kNoGameDataFoundError;
|
||||
|
||||
// If the GUI options were updated, we catch this here and update them in the users config
|
||||
// file transparently.
|
||||
Common::String lang = getGameGUIOptionsDescriptionLanguage(agdDesc->language);
|
||||
if (agdDesc->flags & ADGF_ADDENGLISH)
|
||||
Common::String lang = getGameGUIOptionsDescriptionLanguage(agdDesc.desc->language);
|
||||
if (agdDesc.desc->flags & ADGF_ADDENGLISH)
|
||||
lang += " " + getGameGUIOptionsDescriptionLanguage(Common::EN_ANY);
|
||||
|
||||
Common::updateGameGUIOptions(agdDesc->guiOptions + _guiOptions, lang);
|
||||
Common::updateGameGUIOptions(agdDesc.desc->guiOptions + _guiOptions, lang);
|
||||
|
||||
GameDescriptor gameDescriptor = toGameDescriptor(*agdDesc, _gameIds);
|
||||
GameDescriptor gameDescriptor = toGameDescriptor(*agdDesc.desc, _gameIds);
|
||||
|
||||
bool showTestingWarning = false;
|
||||
|
||||
|
@ -320,65 +336,13 @@ Common::Error AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine)
|
|||
return Common::kUserCanceled;
|
||||
|
||||
debug(2, "Running %s", gameDescriptor.description().c_str());
|
||||
initSubSystems(agdDesc);
|
||||
if (!createInstance(syst, engine, agdDesc))
|
||||
initSubSystems(agdDesc.desc);
|
||||
if (!createInstance(syst, engine, agdDesc.desc))
|
||||
return Common::kNoGameDataFoundError;
|
||||
else
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
void AdvancedMetaEngine::reportUnknown(const Common::FSNode &path, const ADFilePropertiesMap &filesProps, const ADGameIdList &matchedGameIds, bool useUnknownGameDialog) const {
|
||||
const char *reportCommon = _s("The game in '%s' seems to be an unknown %s engine game "
|
||||
"variant.\n\nPlease report the following data to the ScummVM "
|
||||
"team at %s along with the name of the game you tried to add and "
|
||||
"its version, language, etc.:");
|
||||
Common::String report = Common::String::format(reportCommon, path.getPath().c_str(), getName(), "https://bugs.scummvm.org/");
|
||||
Common::String reportTranslated = Common::String::format(_(reportCommon), path.getPath().c_str(), getName(), "https://bugs.scummvm.org/");
|
||||
Common::String bugtrackerAffectedEngine = getName();
|
||||
|
||||
if (!matchedGameIds.empty()) {
|
||||
report += "\n\n";
|
||||
reportTranslated += "\n\n";
|
||||
report += "Matched game IDs:";
|
||||
reportTranslated += _("Matched game IDs:");
|
||||
report += " ";
|
||||
reportTranslated += " ";
|
||||
|
||||
for (ADGameIdList::const_iterator gameId = matchedGameIds.begin(); gameId != matchedGameIds.end(); ++gameId) {
|
||||
if (gameId != matchedGameIds.begin()) {
|
||||
report += ", ";
|
||||
reportTranslated += ", ";
|
||||
}
|
||||
report += *gameId;
|
||||
reportTranslated += *gameId;
|
||||
}
|
||||
}
|
||||
|
||||
report += "\n\n";
|
||||
reportTranslated += "\n\n";
|
||||
|
||||
reportTranslated.wordWrap(65);
|
||||
Common::String reportLog = report;
|
||||
reportLog.wordWrap(80);
|
||||
|
||||
Common::String unknownFiles;
|
||||
for (ADFilePropertiesMap::const_iterator file = filesProps.begin(); file != filesProps.end(); ++file)
|
||||
unknownFiles += Common::String::format(" {\"%s\", 0, \"%s\", %d},\n", file->_key.c_str(), file->_value.md5.c_str(), file->_value.size);
|
||||
|
||||
report += unknownFiles;
|
||||
reportTranslated += unknownFiles;
|
||||
reportLog += unknownFiles + "\n";
|
||||
|
||||
// Write the original message about the unknown game to the log file
|
||||
g_system->logMessage(LogMessageType::kInfo, reportLog.c_str());
|
||||
|
||||
// Check if the GUI is running, show the UnknownGameDialog and print the translated unknown game information
|
||||
if (GUI::GuiManager::hasInstance() && g_gui.isActive() && useUnknownGameDialog == true) {
|
||||
UnknownGameDialog dialog(report, reportTranslated, bugtrackerAffectedEngine);
|
||||
dialog.runModal();
|
||||
}
|
||||
}
|
||||
|
||||
void AdvancedMetaEngine::composeFileHashMap(FileMap &allFiles, const Common::FSList &fslist, int depth, const Common::String &parentName) const {
|
||||
if (depth <= 0)
|
||||
return;
|
||||
|
@ -419,7 +383,7 @@ void AdvancedMetaEngine::composeFileHashMap(FileMap &allFiles, const Common::FSL
|
|||
}
|
||||
}
|
||||
|
||||
bool AdvancedMetaEngine::getFileProperties(const Common::FSNode &parent, const FileMap &allFiles, const ADGameDescription &game, const Common::String fname, ADFileProperties &fileProps) const {
|
||||
bool AdvancedMetaEngine::getFileProperties(const Common::FSNode &parent, const FileMap &allFiles, const ADGameDescription &game, const Common::String fname, FileProperties &fileProps) const {
|
||||
// FIXME/TODO: We don't handle the case that a file is listed as a regular
|
||||
// file and as one with resource fork.
|
||||
|
||||
|
@ -449,8 +413,9 @@ bool AdvancedMetaEngine::getFileProperties(const Common::FSNode &parent, const F
|
|||
return true;
|
||||
}
|
||||
|
||||
ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra, bool useUnknownGameDialog) const {
|
||||
ADFilePropertiesMap filesProps;
|
||||
ADDetectedGames AdvancedMetaEngine::detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra) const {
|
||||
FilePropertiesMap filesProps;
|
||||
ADDetectedGames matched;
|
||||
|
||||
const ADGameFileDescription *fileDesc;
|
||||
const ADGameDescription *g;
|
||||
|
@ -465,7 +430,7 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons
|
|||
|
||||
for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) {
|
||||
Common::String fname = fileDesc->fileName;
|
||||
ADFileProperties tmp;
|
||||
FileProperties tmp;
|
||||
|
||||
if (filesProps.contains(fname))
|
||||
continue;
|
||||
|
@ -477,8 +442,6 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons
|
|||
}
|
||||
}
|
||||
|
||||
ADGameDescList matched;
|
||||
ADGameIdList matchedGameIds;
|
||||
int maxFilesMatched = 0;
|
||||
bool gotAnyMatchesWithAllFiles = false;
|
||||
|
||||
|
@ -486,7 +449,6 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons
|
|||
uint i;
|
||||
for (i = 0, descPtr = _gameDescriptors; ((const ADGameDescription *)descPtr)->gameId != nullptr; descPtr += _descItemSize, ++i) {
|
||||
g = (const ADGameDescription *)descPtr;
|
||||
bool fileMissing = false;
|
||||
|
||||
// Do not even bother to look at entries which do not have matching
|
||||
// language and platform (if specified).
|
||||
|
@ -499,34 +461,33 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons
|
|||
if ((_flags & kADFlagUseExtraAsHint) && !extra.empty() && g->extra != extra)
|
||||
continue;
|
||||
|
||||
ADDetectedGame game(g);
|
||||
bool allFilesPresent = true;
|
||||
int curFilesMatched = 0;
|
||||
bool hashOrSizeMismatch = false;
|
||||
|
||||
// Try to match all files for this game
|
||||
for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) {
|
||||
for (fileDesc = game.desc->filesDescriptions; fileDesc->fileName; fileDesc++) {
|
||||
Common::String tstr = fileDesc->fileName;
|
||||
|
||||
if (!filesProps.contains(tstr)) {
|
||||
fileMissing = true;
|
||||
allFilesPresent = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (hashOrSizeMismatch)
|
||||
game.matchedFiles[tstr] = filesProps[tstr];
|
||||
|
||||
if (game.hasUnknownFiles)
|
||||
continue;
|
||||
|
||||
if (fileDesc->md5 != nullptr && fileDesc->md5 != filesProps[tstr].md5) {
|
||||
debug(3, "MD5 Mismatch. Skipping (%s) (%s)", fileDesc->md5, filesProps[tstr].md5.c_str());
|
||||
fileMissing = true;
|
||||
hashOrSizeMismatch = true;
|
||||
game.hasUnknownFiles = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fileDesc->fileSize != -1 && fileDesc->fileSize != filesProps[tstr].size) {
|
||||
debug(3, "Size Mismatch. Skipping");
|
||||
fileMissing = true;
|
||||
hashOrSizeMismatch = true;
|
||||
game.hasUnknownFiles = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -543,13 +504,12 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons
|
|||
// Potentially this could rule out variants where some particular file
|
||||
// is really missing, but the developers should better know about such
|
||||
// cases.
|
||||
if (allFilesPresent) {
|
||||
gotAnyMatchesWithAllFiles = true;
|
||||
if (matchedGameIds.empty() || strcmp(matchedGameIds.back(), g->gameId) != 0)
|
||||
matchedGameIds.push_back(g->gameId);
|
||||
if (allFilesPresent && !gotAnyMatchesWithAllFiles) {
|
||||
if (matched.empty() || strcmp(matched.back().desc->gameId, g->gameId) != 0)
|
||||
matched.push_back(game);
|
||||
}
|
||||
|
||||
if (!fileMissing) {
|
||||
if (allFilesPresent && !game.hasUnknownFiles) {
|
||||
debug(2, "Found game: %s (%s %s/%s) (%d)", g->gameId, g->extra,
|
||||
getPlatformDescription(g->platform), getLanguageDescription(g->language), i);
|
||||
|
||||
|
@ -558,37 +518,29 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons
|
|||
maxFilesMatched = curFilesMatched;
|
||||
|
||||
matched.clear(); // Remove any prior, lower ranked matches.
|
||||
matched.push_back(g);
|
||||
matched.push_back(game);
|
||||
} else if (curFilesMatched == maxFilesMatched) {
|
||||
matched.push_back(g);
|
||||
matched.push_back(game);
|
||||
} else {
|
||||
debug(2, " ... skipped");
|
||||
}
|
||||
|
||||
gotAnyMatchesWithAllFiles = true;
|
||||
} else {
|
||||
debug(5, "Skipping game: %s (%s %s/%s) (%d)", g->gameId, g->extra,
|
||||
getPlatformDescription(g->platform), getLanguageDescription(g->language), i);
|
||||
}
|
||||
}
|
||||
|
||||
// We didn't find a match
|
||||
if (matched.empty()) {
|
||||
if (!filesProps.empty() && gotAnyMatchesWithAllFiles) {
|
||||
reportUnknown(parent, filesProps, matchedGameIds, useUnknownGameDialog);
|
||||
}
|
||||
|
||||
// Filename based fallback
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
const ADGameDescription *AdvancedMetaEngine::detectGameFilebased(const FileMap &allFiles, const Common::FSList &fslist, const ADFileBasedFallback *fileBasedFallback, ADFilePropertiesMap *filesProps) const {
|
||||
ADDetectedGame AdvancedMetaEngine::detectGameFilebased(const FileMap &allFiles, const Common::FSList &fslist, const ADFileBasedFallback *fileBasedFallback) const {
|
||||
const ADFileBasedFallback *ptr;
|
||||
const char* const* filenames;
|
||||
|
||||
int maxNumMatchedFiles = 0;
|
||||
const ADGameDescription *matchedDesc = 0;
|
||||
ADDetectedGame result;
|
||||
|
||||
for (ptr = fileBasedFallback; ptr->desc; ++ptr) {
|
||||
const ADGameDescription *agdesc = ptr->desc;
|
||||
|
@ -609,25 +561,26 @@ const ADGameDescription *AdvancedMetaEngine::detectGameFilebased(const FileMap &
|
|||
debug(4, "Matched: %s", agdesc->gameId);
|
||||
|
||||
if (numMatchedFiles > maxNumMatchedFiles) {
|
||||
matchedDesc = agdesc;
|
||||
maxNumMatchedFiles = numMatchedFiles;
|
||||
|
||||
debug(4, "and overridden");
|
||||
|
||||
if (filesProps) {
|
||||
for (filenames = ptr->filenames; *filenames; ++filenames) {
|
||||
ADFileProperties tmp;
|
||||
ADDetectedGame game(agdesc);
|
||||
game.hasUnknownFiles = true;
|
||||
|
||||
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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue