ALL: Sync with ScummVM - rev. 2586ca2345

This commit is contained in:
Bastien Bouclet 2018-07-31 18:24:44 +02:00
parent ff56446d6a
commit 53759fe53a
214 changed files with 17297 additions and 12700 deletions

View file

@ -33,32 +33,16 @@
#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;
}
/**
@ -67,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";
@ -89,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;
@ -148,35 +139,46 @@ bool cleanupPirated(ADGameDescList &matched) {
}
GameList AdvancedMetaEngine::detectGames(const Common::FSList &fslist) 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, "");
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);
}
}
@ -214,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;
@ -235,18 +236,6 @@ Common::Error AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine)
path = ConfMan.get("path");
} else {
path = ".";
// This situation may happen only when game was
// launched from a command line with wrong target and
// no path was provided.
//
// A dummy entry will get created and will keep game path
// We mark this entry, so it will not be added to the
// config file.
//
// Fixes bug #1544799
ConfMan.setBool("autoadded", true);
warning("No path was provided. Assuming the data files are in the current directory");
}
Common::FSNode dir(path);
@ -264,46 +253,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;
@ -311,53 +297,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) const {
Common::String report = Common::String::format(
_("The game in '%s' seems to be an unknown %s engine game "
"variant.\n\nPlease report the following data to the ResidualVM "
"team at %s along with the name of the game you tried to add and "
"its version, language, etc.:"),
path.getPath().c_str(), getName(), "https://github.com/residualvm/residualvm/issues");
if (matchedGameIds.size()) {
report += "\n\n";
report += _("Matched game IDs:");
report += " ";
for (ADGameIdList::const_iterator gameId = matchedGameIds.begin(); gameId != matchedGameIds.end(); ++gameId) {
if (gameId != matchedGameIds.begin()) {
report += ", ";
}
report += *gameId;
}
}
report += "\n\n";
report.wordWrap(80);
for (ADFilePropertiesMap::const_iterator file = filesProps.begin(); file != filesProps.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";
g_system->logMessage(LogMessageType::kInfo, report.c_str());
}
void AdvancedMetaEngine::composeFileHashMap(FileMap &allFiles, const Common::FSList &fslist, int depth, const Common::String &parentName) const {
if (depth <= 0)
return;
@ -398,7 +351,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.
@ -428,8 +381,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) 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;
@ -439,12 +393,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;
@ -456,16 +410,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).
@ -478,34 +429,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;
}
@ -522,13 +472,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);
@ -537,37 +486,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);
}
// 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;
@ -588,35 +529,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;
}
@ -625,17 +567,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)