2007-05-30 21:56:52 +00:00
|
|
|
/* ScummVM - Graphic Adventure Engine
|
|
|
|
*
|
|
|
|
* ScummVM is the legal property of its developers, whose names
|
|
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
|
|
* file distributed with this source distribution.
|
2006-10-02 22:21:57 +00:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
2014-02-18 01:34:20 +00:00
|
|
|
*
|
2006-10-02 22:21:57 +00:00
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
2014-02-18 01:34:20 +00:00
|
|
|
*
|
2006-10-02 22:21:57 +00:00
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2009-01-30 05:25:17 +00:00
|
|
|
#include "common/debug.h"
|
2006-10-02 22:21:57 +00:00
|
|
|
#include "common/util.h"
|
|
|
|
#include "common/file.h"
|
2010-05-10 18:23:54 +00:00
|
|
|
#include "common/macresman.h"
|
2006-10-02 22:21:57 +00:00
|
|
|
#include "common/md5.h"
|
2006-11-12 03:23:29 +00:00
|
|
|
#include "common/config-manager.h"
|
2021-07-27 21:44:10 +00:00
|
|
|
#include "common/punycode.h"
|
2011-06-06 10:26:34 +00:00
|
|
|
#include "common/system.h"
|
2011-04-24 08:34:27 +00:00
|
|
|
#include "common/textconsole.h"
|
2021-11-30 22:46:41 +00:00
|
|
|
#include "common/tokenizer.h"
|
2011-06-06 10:26:34 +00:00
|
|
|
#include "common/translation.h"
|
2013-05-16 21:18:09 +00:00
|
|
|
#include "gui/EventRecorder.h"
|
2020-02-03 07:10:52 +00:00
|
|
|
#include "gui/gui-manager.h"
|
|
|
|
#include "gui/message.h"
|
2009-01-30 05:25:17 +00:00
|
|
|
#include "engines/advancedDetector.h"
|
2011-06-14 13:25:33 +00:00
|
|
|
#include "engines/obsolete.h"
|
2008-03-14 14:05:49 +00:00
|
|
|
|
2020-09-12 05:00:53 +00:00
|
|
|
/**
|
|
|
|
* Adapter to be able to use Common::Archive based code from the AD.
|
|
|
|
*/
|
|
|
|
class FileMapArchive : public Common::Archive {
|
|
|
|
public:
|
2020-10-11 21:14:39 +00:00
|
|
|
FileMapArchive(const AdvancedMetaEngineDetection::FileMap &fileMap) : _fileMap(fileMap) {}
|
2020-09-12 05:00:53 +00:00
|
|
|
|
2021-08-01 23:33:22 +00:00
|
|
|
bool hasFile(const Common::Path &path) const override {
|
|
|
|
Common::String name = path.toString();
|
2020-09-12 05:00:53 +00:00
|
|
|
return _fileMap.contains(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
int listMembers(Common::ArchiveMemberList &list) const override {
|
|
|
|
int files = 0;
|
2020-10-11 21:14:39 +00:00
|
|
|
for (AdvancedMetaEngineDetection::FileMap::const_iterator it = _fileMap.begin(); it != _fileMap.end(); ++it) {
|
2020-09-12 05:00:53 +00:00
|
|
|
list.push_back(Common::ArchiveMemberPtr(new Common::FSNode(it->_value)));
|
|
|
|
++files;
|
|
|
|
}
|
|
|
|
|
|
|
|
return files;
|
|
|
|
}
|
|
|
|
|
2021-08-01 23:33:22 +00:00
|
|
|
const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override {
|
|
|
|
Common::String name = path.toString();
|
2020-10-11 21:14:39 +00:00
|
|
|
AdvancedMetaEngineDetection::FileMap::const_iterator it = _fileMap.find(name);
|
2020-09-12 05:00:53 +00:00
|
|
|
if (it == _fileMap.end()) {
|
|
|
|
return Common::ArchiveMemberPtr();
|
|
|
|
}
|
|
|
|
|
|
|
|
return Common::ArchiveMemberPtr(new Common::FSNode(it->_value));
|
|
|
|
}
|
|
|
|
|
2021-08-01 23:33:22 +00:00
|
|
|
Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override {
|
|
|
|
Common::String name = path.toString();
|
2020-10-20 01:32:09 +00:00
|
|
|
Common::FSNode fsNode = _fileMap.getValOrDefault(name);
|
2020-09-12 05:00:53 +00:00
|
|
|
return fsNode.createReadStream();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2020-10-11 21:14:39 +00:00
|
|
|
const AdvancedMetaEngineDetection::FileMap &_fileMap;
|
2020-09-12 05:00:53 +00:00
|
|
|
};
|
|
|
|
|
2020-10-21 17:13:12 +00:00
|
|
|
static Common::String sanitizeName(const char *name, int maxLen) {
|
2018-05-06 14:26:57 +00:00
|
|
|
Common::String res;
|
2020-10-21 20:12:16 +00:00
|
|
|
Common::String word;
|
2020-10-22 10:13:13 +00:00
|
|
|
Common::String lastWord;
|
|
|
|
const char *origname = name;
|
2010-01-03 21:07:40 +00:00
|
|
|
|
2020-10-21 20:12:16 +00:00
|
|
|
do {
|
2020-10-21 17:13:12 +00:00
|
|
|
if (Common::isAlnum(*name)) {
|
2020-10-21 20:12:16 +00:00
|
|
|
word += tolower(*name);
|
|
|
|
} else {
|
|
|
|
// Skipping short words and "the"
|
|
|
|
if ((word.size() > 2 && !word.equals("the")) || (!word.empty() && Common::isDigit(word[0]))) {
|
|
|
|
// Adding first word, or when word fits
|
2021-03-12 19:51:39 +00:00
|
|
|
if (res.empty() || (int)word.size() < maxLen)
|
2020-10-21 20:12:16 +00:00
|
|
|
res += word;
|
|
|
|
|
|
|
|
maxLen -= word.size();
|
|
|
|
}
|
2020-10-22 09:58:11 +00:00
|
|
|
|
2020-10-22 11:59:55 +00:00
|
|
|
if ((*name && *(name + 1) == 0) || !*name) {
|
2020-10-22 10:13:13 +00:00
|
|
|
if (res.empty()) // Make sure that we add at least something
|
|
|
|
res += word.empty() ? lastWord : word;
|
|
|
|
|
2020-10-22 09:58:11 +00:00
|
|
|
break;
|
2020-10-22 10:13:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!word.empty())
|
|
|
|
lastWord = word;
|
|
|
|
|
|
|
|
word.clear();
|
2020-10-21 17:13:12 +00:00
|
|
|
}
|
2020-10-21 20:12:16 +00:00
|
|
|
if (*name)
|
|
|
|
name++;
|
2020-10-22 09:58:11 +00:00
|
|
|
} while (maxLen > 0);
|
2006-11-12 03:23:29 +00:00
|
|
|
|
2020-10-22 10:13:13 +00:00
|
|
|
if (res.empty())
|
|
|
|
error("AdvancedDetector: Incorrect extra in game: \"%s\"", origname);
|
|
|
|
|
2018-05-06 14:26:57 +00:00
|
|
|
return res;
|
2017-12-02 16:14:22 +00:00
|
|
|
}
|
|
|
|
|
2007-01-29 23:25:51 +00:00
|
|
|
/**
|
2007-02-13 13:19:43 +00:00
|
|
|
* Generate a preferred target value as
|
|
|
|
* GAMEID-PLAFORM-LANG
|
2007-02-13 13:42:28 +00:00
|
|
|
* or (if ADGF_DEMO has been set)
|
2007-02-13 13:19:43 +00:00
|
|
|
* GAMEID-demo-PLAFORM-LANG
|
2007-01-29 23:25:51 +00:00
|
|
|
*/
|
2021-06-10 13:35:32 +00:00
|
|
|
static Common::String generatePreferredTarget(const ADGameDescription *desc, int maxLen, Common::String targetID) {
|
2018-05-06 14:26:57 +00:00
|
|
|
Common::String res;
|
|
|
|
|
2021-06-10 13:35:32 +00:00
|
|
|
if (!targetID.empty()) {
|
|
|
|
res = targetID;
|
|
|
|
} else if (desc->flags & ADGF_AUTOGENTARGET && desc->extra && *desc->extra) {
|
2020-10-21 17:13:12 +00:00
|
|
|
res = sanitizeName(desc->extra, maxLen);
|
2018-05-06 14:26:57 +00:00
|
|
|
} else {
|
|
|
|
res = desc->gameId;
|
|
|
|
}
|
2007-01-29 23:25:51 +00:00
|
|
|
|
2007-02-13 13:42:28 +00:00
|
|
|
if (desc->flags & ADGF_DEMO) {
|
2007-02-13 13:00:18 +00:00
|
|
|
res = res + "-demo";
|
|
|
|
}
|
|
|
|
|
2008-04-10 21:41:57 +00:00
|
|
|
if (desc->flags & ADGF_CD) {
|
|
|
|
res = res + "-cd";
|
|
|
|
}
|
|
|
|
|
2020-10-07 17:50:27 +00:00
|
|
|
if (desc->flags & ADGF_REMASTERED) {
|
|
|
|
res = res + "-remastered";
|
|
|
|
}
|
2020-10-09 17:04:07 +00:00
|
|
|
|
2013-09-08 21:34:01 +00:00
|
|
|
if (desc->platform != Common::kPlatformDOS && desc->platform != Common::kPlatformUnknown && !(desc->flags & ADGF_DROPPLATFORM)) {
|
2007-01-29 23:25:51 +00:00
|
|
|
res = res + "-" + getPlatformAbbrev(desc->platform);
|
|
|
|
}
|
|
|
|
|
2009-01-29 22:13:01 +00:00
|
|
|
if (desc->language != Common::EN_ANY && desc->language != Common::UNK_LANG && !(desc->flags & ADGF_DROPLANGUAGE)) {
|
2007-01-29 23:25:51 +00:00
|
|
|
res = res + "-" + getLanguageCode(desc->language);
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2021-06-10 13:35:32 +00:00
|
|
|
DetectedGame AdvancedMetaEngineDetection::toDetectedGame(const ADDetectedGame &adGame, ADDetectedGameExtraInfo *extraInfo) const {
|
2018-05-06 14:26:57 +00:00
|
|
|
const ADGameDescription *desc = adGame.desc;
|
2016-03-07 17:46:08 +00:00
|
|
|
|
2018-05-06 14:26:57 +00:00
|
|
|
const char *title;
|
|
|
|
const char *extra;
|
|
|
|
if (desc->flags & ADGF_USEEXTRAASTITLE) {
|
|
|
|
title = desc->extra;
|
|
|
|
extra = "";
|
|
|
|
} else {
|
|
|
|
const PlainGameDescriptor *pgd = findPlainGameDescriptor(desc->gameId, _gameIds);
|
2018-11-11 08:16:11 +00:00
|
|
|
if (pgd) {
|
|
|
|
title = pgd->description;
|
|
|
|
} else {
|
|
|
|
title = "";
|
|
|
|
}
|
2018-05-06 14:26:57 +00:00
|
|
|
extra = desc->extra;
|
2007-06-12 12:22:25 +00:00
|
|
|
}
|
|
|
|
|
2021-06-10 13:35:32 +00:00
|
|
|
if (extraInfo) {
|
|
|
|
if (!extraInfo->gameName.empty())
|
|
|
|
title = extraInfo->gameName.c_str();
|
|
|
|
}
|
|
|
|
|
2021-04-16 12:14:40 +00:00
|
|
|
DetectedGame game(getEngineId(), desc->gameId, title, desc->language, desc->platform, extra, ((desc->flags & (ADGF_UNSUPPORTED | ADGF_WARNING)) != 0));
|
2018-05-06 14:26:57 +00:00
|
|
|
game.hasUnknownFiles = adGame.hasUnknownFiles;
|
|
|
|
game.matchedFiles = adGame.matchedFiles;
|
2021-06-10 13:35:32 +00:00
|
|
|
|
2021-11-06 12:19:51 +00:00
|
|
|
// Now specify the computation method for each file entry.
|
|
|
|
// TODO: This could be potentially overridden by use of upper part of adGame.fileType
|
|
|
|
// so, individual files could have their own computation method
|
|
|
|
for (FilePropertiesMap::iterator file = game.matchedFiles.begin(); file != game.matchedFiles.end(); ++file) {
|
|
|
|
if (desc->flags & ADGF_MACRESFORK)
|
|
|
|
file->_value.md5prop = (MD5Properties)(file->_value.md5prop | kMD5MacResFork);
|
|
|
|
if (desc->flags & ADGF_TAILMD5)
|
|
|
|
file->_value.md5prop = (MD5Properties)(file->_value.md5prop | kMD5Tail);
|
|
|
|
}
|
|
|
|
|
2021-06-10 13:35:32 +00:00
|
|
|
if (extraInfo && !extraInfo->targetID.empty()) {
|
|
|
|
game.preferredTarget = generatePreferredTarget(desc, _maxAutogenLength, extraInfo->targetID);
|
|
|
|
} else {
|
|
|
|
game.preferredTarget = generatePreferredTarget(desc, _maxAutogenLength, Common::String());
|
|
|
|
}
|
2016-03-07 17:46:08 +00:00
|
|
|
|
2018-05-06 14:26:57 +00:00
|
|
|
game.gameSupportLevel = kStableGame;
|
|
|
|
if (desc->flags & ADGF_UNSTABLE)
|
|
|
|
game.gameSupportLevel = kUnstableGame;
|
|
|
|
else if (desc->flags & ADGF_TESTING)
|
|
|
|
game.gameSupportLevel = kTestingGame;
|
2020-11-08 19:05:35 +00:00
|
|
|
else if (desc->flags & ADGF_UNSUPPORTED)
|
2021-04-16 11:28:43 +00:00
|
|
|
game.gameSupportLevel = kUnsupportedGame;
|
2021-04-16 12:14:40 +00:00
|
|
|
else if (desc->flags & ADGF_WARNING)
|
|
|
|
game.gameSupportLevel = kWarningGame;
|
2016-03-07 17:46:08 +00:00
|
|
|
|
2018-05-06 14:26:57 +00:00
|
|
|
game.setGUIOptions(desc->guiOptions + _guiOptions);
|
|
|
|
game.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(desc->language));
|
2007-12-31 14:45:38 +00:00
|
|
|
|
2018-05-06 14:26:57 +00:00
|
|
|
if (desc->flags & ADGF_ADDENGLISH)
|
|
|
|
game.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(Common::EN_ANY));
|
2009-06-06 17:56:41 +00:00
|
|
|
|
2018-05-06 14:26:57 +00:00
|
|
|
if (_flags & kADFlagUseExtraAsHint)
|
|
|
|
game.extra = desc->extra;
|
2010-08-01 21:17:00 +00:00
|
|
|
|
2018-05-06 14:26:57 +00:00
|
|
|
return game;
|
2007-06-12 12:22:25 +00:00
|
|
|
}
|
|
|
|
|
2021-04-17 21:19:16 +00:00
|
|
|
bool AdvancedMetaEngineDetection::cleanupPirated(ADDetectedGames &matched) const {
|
2011-06-14 15:26:55 +00:00
|
|
|
// OKay, now let's sense presence of pirated games
|
2010-08-25 11:51:06 +00:00
|
|
|
if (!matched.empty()) {
|
|
|
|
for (uint j = 0; j < matched.size();) {
|
2017-12-02 16:14:22 +00:00
|
|
|
if (matched[j].desc->flags & ADGF_PIRATED)
|
2010-08-25 11:51:06 +00:00
|
|
|
matched.remove_at(j);
|
2010-08-25 12:03:43 +00:00
|
|
|
else
|
|
|
|
++j;
|
2010-08-25 11:51:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// We ruled out all variants and now have nothing
|
|
|
|
if (matched.empty()) {
|
2014-08-06 12:08:57 +00:00
|
|
|
warning("Illegitimate game copy detected. We provide no support in such cases");
|
2020-02-03 07:10:52 +00:00
|
|
|
if (GUI::GuiManager::hasInstance()) {
|
2020-02-03 07:33:52 +00:00
|
|
|
GUI::MessageDialog dialog(_("Illegitimate game copy detected. We provide no support in such cases"));
|
2020-02-03 07:10:52 +00:00
|
|
|
dialog.runModal();
|
|
|
|
};
|
2010-08-25 11:51:06 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-11-29 00:21:20 +00:00
|
|
|
DetectedGames AdvancedMetaEngineDetection::detectGames(const Common::FSList &fslist) {
|
2011-06-14 16:02:09 +00:00
|
|
|
FileMap allFiles;
|
|
|
|
|
|
|
|
if (fslist.empty())
|
2017-12-02 16:14:22 +00:00
|
|
|
return DetectedGames();
|
2011-06-14 16:02:09 +00:00
|
|
|
|
2021-11-29 00:21:20 +00:00
|
|
|
// Sometimes this method is called directly, so we have to build the maps, especially
|
|
|
|
// the _directoryGlobsMap
|
|
|
|
preprocessDescriptions();
|
|
|
|
|
2011-06-14 16:02:09 +00:00
|
|
|
// Compose a hashmap of all files in fslist.
|
|
|
|
composeFileHashMap(allFiles, fslist, (_maxScanDepth == 0 ? 1 : _maxScanDepth));
|
|
|
|
|
|
|
|
// Run the detector on this
|
2017-12-02 16:14:22 +00:00
|
|
|
ADDetectedGames matches = detectGame(fslist.begin()->getParent(), allFiles, Common::UNK_LANG, Common::kPlatformUnknown, "");
|
2007-01-29 23:25:51 +00:00
|
|
|
|
2017-12-02 16:14:22 +00:00
|
|
|
cleanupPirated(matches);
|
|
|
|
|
|
|
|
DetectedGames detectedGames;
|
|
|
|
for (uint i = 0; i < matches.size(); i++) {
|
|
|
|
DetectedGame game = toDetectedGame(matches[i]);
|
|
|
|
|
2021-04-19 21:28:47 +00:00
|
|
|
if (game.hasUnknownFiles && !canPlayUnknownVariants()) {
|
2017-12-02 16:14:22 +00:00
|
|
|
game.canBeAdded = false;
|
2007-01-29 23:25:51 +00:00
|
|
|
}
|
2017-12-02 16:14:22 +00:00
|
|
|
|
|
|
|
detectedGames.push_back(game);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool foundKnownGames = false;
|
|
|
|
for (uint i = 0; i < detectedGames.size(); i++) {
|
2021-04-19 21:28:47 +00:00
|
|
|
foundKnownGames |= !detectedGames[i].hasUnknownFiles;
|
2017-12-02 16:14:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!foundKnownGames) {
|
|
|
|
// Use fallback detector if there were no matches by other means
|
2021-06-10 13:12:20 +00:00
|
|
|
ADDetectedGameExtraInfo *extraInfo = nullptr;
|
|
|
|
ADDetectedGame fallbackDetectionResult = fallbackDetect(allFiles, fslist, &extraInfo);
|
2017-12-02 16:14:22 +00:00
|
|
|
|
|
|
|
if (fallbackDetectionResult.desc) {
|
2021-06-10 13:35:32 +00:00
|
|
|
DetectedGame fallbackDetectedGame = toDetectedGame(fallbackDetectionResult, extraInfo);
|
2017-12-02 16:14:22 +00:00
|
|
|
|
2021-06-10 13:12:20 +00:00
|
|
|
if (extraInfo != nullptr) {
|
|
|
|
// then it's our duty to free it
|
|
|
|
delete extraInfo;
|
2021-06-10 13:35:32 +00:00
|
|
|
} else {
|
|
|
|
// don't add fallback when we are specifying the targetID
|
|
|
|
fallbackDetectedGame.preferredTarget += "-fallback";
|
2021-06-10 13:12:20 +00:00
|
|
|
}
|
|
|
|
|
2017-12-02 16:14:22 +00:00
|
|
|
detectedGames.push_back(fallbackDetectedGame);
|
2010-11-07 17:15:27 +00:00
|
|
|
}
|
2007-01-29 23:25:51 +00:00
|
|
|
}
|
2006-11-12 03:23:29 +00:00
|
|
|
|
|
|
|
return detectedGames;
|
|
|
|
}
|
|
|
|
|
2020-10-11 21:14:39 +00:00
|
|
|
const ExtraGuiOptions AdvancedMetaEngineDetection::getExtraGuiOptions(const Common::String &target) const {
|
2012-02-29 15:48:25 +00:00
|
|
|
if (!_extraGuiOptions)
|
|
|
|
return ExtraGuiOptions();
|
|
|
|
|
2012-03-31 10:55:03 +00:00
|
|
|
ExtraGuiOptions options;
|
|
|
|
|
|
|
|
// If there isn't any target specified, return all available GUI options.
|
|
|
|
// Only used when an engine starts in order to set option defaults.
|
|
|
|
if (target.empty()) {
|
|
|
|
for (const ADExtraGuiOptionsMap *entry = _extraGuiOptions; entry->guioFlag; ++entry)
|
|
|
|
options.push_back(entry->option);
|
|
|
|
|
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
2012-02-29 15:48:25 +00:00
|
|
|
// Query the GUI options
|
|
|
|
const Common::String guiOptionsString = ConfMan.get("guioptions", target);
|
|
|
|
const Common::String guiOptions = parseGameGUIOptions(guiOptionsString);
|
|
|
|
|
|
|
|
// Add all the applying extra GUI options.
|
|
|
|
for (const ADExtraGuiOptionsMap *entry = _extraGuiOptions; entry->guioFlag; ++entry) {
|
|
|
|
if (guiOptions.contains(entry->guioFlag))
|
|
|
|
options.push_back(entry->option);
|
|
|
|
}
|
|
|
|
|
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
2021-11-29 00:21:20 +00:00
|
|
|
Common::Error AdvancedMetaEngineDetection::createInstance(OSystem *syst, Engine **engine) {
|
2008-03-14 13:59:31 +00:00
|
|
|
assert(engine);
|
|
|
|
|
2006-11-12 03:23:29 +00:00
|
|
|
Common::Language language = Common::UNK_LANG;
|
|
|
|
Common::Platform platform = Common::kPlatformUnknown;
|
2008-11-15 13:19:40 +00:00
|
|
|
Common::String extra;
|
2006-11-12 03:23:29 +00:00
|
|
|
|
|
|
|
if (ConfMan.hasKey("language"))
|
|
|
|
language = Common::parseLanguage(ConfMan.get("language"));
|
|
|
|
if (ConfMan.hasKey("platform"))
|
|
|
|
platform = Common::parsePlatform(ConfMan.get("platform"));
|
2011-06-14 15:13:02 +00:00
|
|
|
if (_flags & kADFlagUseExtraAsHint) {
|
2007-12-31 14:45:38 +00:00
|
|
|
if (ConfMan.hasKey("extra"))
|
|
|
|
extra = ConfMan.get("extra");
|
2011-06-14 15:13:02 +00:00
|
|
|
}
|
2006-11-12 03:23:29 +00:00
|
|
|
|
|
|
|
Common::String gameid = ConfMan.get("gameid");
|
|
|
|
|
2008-07-29 00:50:12 +00:00
|
|
|
Common::String path;
|
|
|
|
if (ConfMan.hasKey("path")) {
|
|
|
|
path = ConfMan.get("path");
|
|
|
|
} else {
|
|
|
|
path = ".";
|
|
|
|
warning("No path was provided. Assuming the data files are in the current directory");
|
|
|
|
}
|
2009-01-29 22:13:01 +00:00
|
|
|
Common::FSNode dir(path);
|
|
|
|
Common::FSList files;
|
2019-01-30 05:24:06 +00:00
|
|
|
if (!dir.isDirectory() || !dir.getChildren(files, Common::FSNode::kListAll)) {
|
2008-07-29 00:50:12 +00:00
|
|
|
warning("Game data path does not exist or is not a directory (%s)", path.c_str());
|
2009-01-29 22:13:01 +00:00
|
|
|
return Common::kNoGameDataFoundError;
|
2008-07-29 00:50:12 +00:00
|
|
|
}
|
|
|
|
|
2011-06-14 16:02:09 +00:00
|
|
|
if (files.empty())
|
|
|
|
return Common::kNoGameDataFoundError;
|
|
|
|
|
2021-11-29 00:21:20 +00:00
|
|
|
// Sometimes this method is called directly, so we have to build the maps, especially
|
|
|
|
// the _directoryGlobsMap
|
|
|
|
preprocessDescriptions();
|
|
|
|
|
2011-06-14 16:02:09 +00:00
|
|
|
// Compose a hashmap of all files in fslist.
|
|
|
|
FileMap allFiles;
|
|
|
|
composeFileHashMap(allFiles, files, (_maxScanDepth == 0 ? 1 : _maxScanDepth));
|
|
|
|
|
2021-05-31 21:13:08 +00:00
|
|
|
// Clear md5 cache before each detection starts, just in case.
|
|
|
|
MD5Man.clear();
|
|
|
|
|
2011-06-14 16:02:09 +00:00
|
|
|
// Run the detector on this
|
2017-12-02 16:14:22 +00:00
|
|
|
ADDetectedGames matches = detectGame(files.begin()->getParent(), allFiles, language, platform, extra);
|
2006-11-12 03:23:29 +00:00
|
|
|
|
2010-08-25 11:51:06 +00:00
|
|
|
if (cleanupPirated(matches))
|
|
|
|
return Common::kNoGameDataFoundError;
|
|
|
|
|
2017-12-02 16:14:22 +00:00
|
|
|
ADDetectedGame agdDesc;
|
|
|
|
for (uint i = 0; i < matches.size(); i++) {
|
2021-04-19 21:28:47 +00:00
|
|
|
if (matches[i].desc->gameId == gameid && (!matches[i].hasUnknownFiles || canPlayUnknownVariants())) {
|
2017-12-02 16:14:22 +00:00
|
|
|
agdDesc = matches[i];
|
|
|
|
break;
|
2006-11-12 03:23:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-02 16:14:22 +00:00
|
|
|
if (!agdDesc.desc) {
|
2008-03-14 13:59:31 +00:00
|
|
|
// Use fallback detector if there were no matches by other means
|
2017-12-02 16:14:22 +00:00
|
|
|
ADDetectedGame fallbackDetectedGame = fallbackDetect(allFiles, files);
|
|
|
|
agdDesc = fallbackDetectedGame;
|
|
|
|
if (agdDesc.desc) {
|
2008-03-15 15:25:49 +00:00
|
|
|
// Seems we found a fallback match. But first perform a basic
|
|
|
|
// sanity check: the gameid must match.
|
2016-09-15 16:39:45 +00:00
|
|
|
if (agdDesc.desc->gameId != gameid)
|
2017-12-02 16:14:22 +00:00
|
|
|
agdDesc = ADDetectedGame();
|
2007-06-12 12:22:25 +00:00
|
|
|
}
|
2006-11-12 03:23:29 +00:00
|
|
|
}
|
|
|
|
|
2017-12-02 16:14:22 +00:00
|
|
|
if (!agdDesc.desc)
|
2009-01-29 22:13:01 +00:00
|
|
|
return Common::kNoGameDataFoundError;
|
2009-07-13 18:31:42 +00:00
|
|
|
|
2020-06-05 17:43:03 +00:00
|
|
|
DetectedGame gameDescriptor = toDetectedGame(agdDesc);
|
|
|
|
|
2009-07-13 18:31:42 +00:00
|
|
|
// If the GUI options were updated, we catch this here and update them in the users config
|
|
|
|
// file transparently.
|
2020-06-05 17:43:03 +00:00
|
|
|
ConfMan.setAndFlush("guioptions", gameDescriptor.getGUIOptions());
|
2011-04-25 20:26:38 +00:00
|
|
|
|
|
|
|
bool showTestingWarning = false;
|
|
|
|
|
|
|
|
#ifdef RELEASE_BUILD
|
|
|
|
showTestingWarning = true;
|
|
|
|
#endif
|
|
|
|
|
2017-12-03 11:19:08 +00:00
|
|
|
if (((gameDescriptor.gameSupportLevel == kUnstableGame
|
|
|
|
|| (gameDescriptor.gameSupportLevel == kTestingGame
|
2011-04-25 20:26:38 +00:00
|
|
|
&& showTestingWarning)))
|
2021-04-16 12:42:34 +00:00
|
|
|
&& !Engine::warnUserAboutUnsupportedGame())
|
2021-04-16 12:14:40 +00:00
|
|
|
return Common::kUserCanceled;
|
|
|
|
|
|
|
|
if (gameDescriptor.gameSupportLevel == kWarningGame
|
|
|
|
&& !Engine::warnUserAboutUnsupportedGame(gameDescriptor.extra))
|
2011-04-25 20:26:38 +00:00
|
|
|
return Common::kUserCanceled;
|
2008-03-14 17:31:04 +00:00
|
|
|
|
2021-04-16 11:28:43 +00:00
|
|
|
if (gameDescriptor.gameSupportLevel == kUnsupportedGame) {
|
2020-11-08 19:05:35 +00:00
|
|
|
Engine::errorUnsupportedGame(gameDescriptor.extra);
|
|
|
|
return Common::kUserCanceled;
|
|
|
|
}
|
|
|
|
|
2021-09-16 08:38:02 +00:00
|
|
|
debug("Running %s", gameDescriptor.description.c_str());
|
|
|
|
for (FilePropertiesMap::const_iterator i = gameDescriptor.matchedFiles.begin(); i != gameDescriptor.matchedFiles.end(); ++i) {
|
|
|
|
debug("%s: %s, %llu bytes.", i->_key.c_str(), i->_value.md5.c_str(), (unsigned long long)i->_value.size);
|
|
|
|
}
|
2017-12-02 16:14:22 +00:00
|
|
|
initSubSystems(agdDesc.desc);
|
2020-08-02 15:07:48 +00:00
|
|
|
|
|
|
|
PluginList pl = EngineMan.getPlugins(PLUGIN_TYPE_ENGINE);
|
|
|
|
Plugin *plugin = nullptr;
|
|
|
|
|
|
|
|
// By this point of time, we should have only one plugin in memory.
|
|
|
|
if (pl.size() == 1) {
|
|
|
|
plugin = pl[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (plugin) {
|
|
|
|
// Call child class's createInstanceMethod.
|
2020-11-17 00:06:05 +00:00
|
|
|
return plugin->get<AdvancedMetaEngine>().createInstance(syst, engine, agdDesc.desc);
|
2020-08-02 15:07:48 +00:00
|
|
|
}
|
|
|
|
|
2020-11-17 00:06:05 +00:00
|
|
|
return Common::Error(Common::kEnginePluginNotFound);
|
2006-11-12 03:23:29 +00:00
|
|
|
}
|
|
|
|
|
2020-10-11 21:14:39 +00:00
|
|
|
void AdvancedMetaEngineDetection::composeFileHashMap(FileMap &allFiles, const Common::FSList &fslist, int depth, const Common::String &parentName) const {
|
2010-06-14 14:51:18 +00:00
|
|
|
if (depth <= 0)
|
2010-06-14 14:50:23 +00:00
|
|
|
return;
|
2007-01-30 02:02:10 +00:00
|
|
|
|
2009-02-21 02:59:45 +00:00
|
|
|
if (fslist.empty())
|
2010-06-14 14:50:23 +00:00
|
|
|
return;
|
2007-01-30 02:02:10 +00:00
|
|
|
|
2009-01-29 22:13:01 +00:00
|
|
|
for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
|
2021-11-30 22:58:48 +00:00
|
|
|
Common::String tstr = ((_flags & kADFlagMatchFullPaths) && !parentName.empty() ? parentName + "/" : "") + file->getName();
|
2017-05-21 20:31:37 +00:00
|
|
|
|
2010-06-14 14:50:23 +00:00
|
|
|
if (file->isDirectory()) {
|
2021-11-29 00:49:18 +00:00
|
|
|
if (!_globsMap.contains(file->getName()))
|
2010-06-15 10:59:23 +00:00
|
|
|
continue;
|
|
|
|
|
2021-11-29 00:49:18 +00:00
|
|
|
Common::FSList files;
|
2010-06-14 14:50:23 +00:00
|
|
|
if (!file->getChildren(files, Common::FSNode::kListAll))
|
|
|
|
continue;
|
|
|
|
|
2017-05-21 20:31:37 +00:00
|
|
|
composeFileHashMap(allFiles, files, depth - 1, tstr);
|
2010-06-14 14:50:23 +00:00
|
|
|
}
|
2008-07-30 15:16:57 +00:00
|
|
|
|
2008-07-29 00:50:12 +00:00
|
|
|
// Strip any trailing dot
|
|
|
|
if (tstr.lastChar() == '.')
|
|
|
|
tstr.deleteLastChar();
|
2007-04-26 20:35:10 +00:00
|
|
|
|
2008-09-30 12:38:44 +00:00
|
|
|
allFiles[tstr] = *file; // Record the presence of this file
|
2008-07-30 15:38:42 +00:00
|
|
|
}
|
2010-06-14 14:50:23 +00:00
|
|
|
}
|
|
|
|
|
2021-05-02 09:53:21 +00:00
|
|
|
/* Singleton Cache Storage for MD5 */
|
|
|
|
|
|
|
|
namespace Common {
|
|
|
|
DECLARE_SINGLETON(MD5CacheManager);
|
|
|
|
}
|
|
|
|
|
2021-11-06 15:50:25 +00:00
|
|
|
// Sync with engines/game.cpp
|
2021-10-14 10:55:44 +00:00
|
|
|
static char flagsToMD5Prefix(uint32 flags) {
|
2021-10-16 11:12:43 +00:00
|
|
|
if (flags & ADGF_MACRESFORK) {
|
|
|
|
if (flags & ADGF_TAILMD5)
|
|
|
|
return 'e';
|
2021-10-14 10:55:44 +00:00
|
|
|
return 'm';
|
2021-10-16 11:12:43 +00:00
|
|
|
}
|
2021-10-14 21:52:28 +00:00
|
|
|
if (flags & ADGF_TAILMD5)
|
|
|
|
return 't';
|
2021-10-14 10:55:44 +00:00
|
|
|
|
|
|
|
return 'f';
|
|
|
|
}
|
|
|
|
|
2021-10-16 10:26:25 +00:00
|
|
|
static bool getFilePropertiesIntern(uint md5Bytes, const AdvancedMetaEngine::FileMap &allFiles, const ADGameDescription &game, const Common::String fname, FileProperties &fileProps);
|
|
|
|
|
2020-10-11 21:14:39 +00:00
|
|
|
bool AdvancedMetaEngineDetection::getFileProperties(const FileMap &allFiles, const ADGameDescription &game, const Common::String fname, FileProperties &fileProps) const {
|
2021-10-14 10:55:44 +00:00
|
|
|
Common::String hashname = Common::String::format("%c:%s:%d", flagsToMD5Prefix(game.flags), fname.c_str(), _md5Bytes);
|
2021-05-02 09:53:21 +00:00
|
|
|
|
|
|
|
if (MD5Man.contains(hashname)) {
|
|
|
|
fileProps.md5 = MD5Man.getMD5(hashname);
|
|
|
|
fileProps.size = MD5Man.getSize(hashname);
|
|
|
|
return true;
|
|
|
|
}
|
2012-06-27 02:25:38 +00:00
|
|
|
|
2021-10-16 10:26:25 +00:00
|
|
|
bool res = getFilePropertiesIntern(_md5Bytes, allFiles, game, fname, fileProps);
|
2020-09-12 05:00:53 +00:00
|
|
|
|
2021-10-16 10:26:25 +00:00
|
|
|
if (res) {
|
|
|
|
MD5Man.setMD5(hashname, fileProps.md5);
|
|
|
|
MD5Man.setSize(hashname, fileProps.size);
|
2012-06-27 02:25:38 +00:00
|
|
|
}
|
|
|
|
|
2021-10-16 10:26:25 +00:00
|
|
|
return res;
|
2012-06-27 02:25:38 +00:00
|
|
|
}
|
|
|
|
|
2020-10-02 05:46:09 +00:00
|
|
|
bool AdvancedMetaEngine::getFilePropertiesExtern(uint md5Bytes, const FileMap &allFiles, const ADGameDescription &game, const Common::String fname, FileProperties &fileProps) const {
|
2021-10-16 10:26:25 +00:00
|
|
|
return getFilePropertiesIntern(md5Bytes, allFiles, game, fname, fileProps);
|
|
|
|
}
|
2020-08-07 13:36:17 +00:00
|
|
|
|
2021-10-16 10:26:25 +00:00
|
|
|
static bool getFilePropertiesIntern(uint md5Bytes, const AdvancedMetaEngine::FileMap &allFiles, const ADGameDescription &game, const Common::String fname, FileProperties &fileProps) {
|
2020-08-07 13:36:17 +00:00
|
|
|
if (game.flags & ADGF_MACRESFORK) {
|
2020-08-07 13:54:17 +00:00
|
|
|
FileMapArchive fileMapArchive(allFiles);
|
|
|
|
|
2020-08-07 13:36:17 +00:00
|
|
|
Common::MacResManager macResMan;
|
|
|
|
|
2020-08-07 13:54:17 +00:00
|
|
|
if (!macResMan.open(fname, fileMapArchive))
|
2020-08-07 13:36:17 +00:00
|
|
|
return false;
|
|
|
|
|
2021-10-16 11:12:43 +00:00
|
|
|
fileProps.md5 = macResMan.computeResForkMD5AsString(md5Bytes, ((game.flags & ADGF_TAILMD5) != 0));
|
2020-08-07 13:36:17 +00:00
|
|
|
fileProps.size = macResMan.getResForkDataSize();
|
|
|
|
|
|
|
|
if (fileProps.size != 0)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!allFiles.contains(fname))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
Common::File testFile;
|
|
|
|
|
|
|
|
if (!testFile.open(allFiles[fname]))
|
|
|
|
return false;
|
|
|
|
|
2021-10-16 10:34:16 +00:00
|
|
|
if (game.flags & ADGF_TAILMD5) {
|
|
|
|
if (testFile.size() > md5Bytes)
|
2021-10-19 18:08:52 +00:00
|
|
|
testFile.seek(-(int64)md5Bytes, SEEK_END);
|
2021-10-16 10:34:16 +00:00
|
|
|
}
|
2021-10-14 21:52:28 +00:00
|
|
|
|
2021-07-04 04:38:40 +00:00
|
|
|
fileProps.size = testFile.size();
|
2020-08-07 13:36:17 +00:00
|
|
|
fileProps.md5 = Common::computeStreamMD5AsString(testFile, md5Bytes);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-11-29 00:21:20 +00:00
|
|
|
ADDetectedGames AdvancedMetaEngineDetection::detectGame(const Common::FSNode &parent, const FileMap &allFiles, Common::Language language, Common::Platform platform, const Common::String &extra) {
|
2017-12-02 16:14:22 +00:00
|
|
|
FilePropertiesMap filesProps;
|
|
|
|
ADDetectedGames matched;
|
2010-06-14 14:50:23 +00:00
|
|
|
|
|
|
|
const ADGameFileDescription *fileDesc;
|
|
|
|
const ADGameDescription *g;
|
|
|
|
const byte *descPtr;
|
|
|
|
|
2021-11-06 15:50:25 +00:00
|
|
|
debugC(3, kDebugGlobalDetection, "Starting detection for engine '%s' in dir '%s'", getEngineId(), parent.getPath().c_str());
|
2010-06-14 14:50:23 +00:00
|
|
|
|
2021-11-28 11:54:59 +00:00
|
|
|
preprocessDescriptions();
|
2021-11-27 19:26:17 +00:00
|
|
|
|
2020-02-26 20:16:14 +00:00
|
|
|
// Check which files are included in some ADGameDescription *and* whether
|
|
|
|
// they are present. Compute MD5s and file sizes for the available files.
|
2017-12-02 15:43:48 +00:00
|
|
|
for (descPtr = _gameDescriptors; ((const ADGameDescription *)descPtr)->gameId != nullptr; descPtr += _descItemSize) {
|
2008-07-30 15:38:42 +00:00
|
|
|
g = (const ADGameDescription *)descPtr;
|
2007-01-30 02:02:10 +00:00
|
|
|
|
2008-07-30 15:38:42 +00:00
|
|
|
for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) {
|
2021-07-27 21:44:10 +00:00
|
|
|
Common::String fname = Common::punycode_decodefilename(fileDesc->fileName);
|
2021-10-19 10:04:18 +00:00
|
|
|
Common::String key = Common::String::format("%c:%s", flagsToMD5Prefix(g->flags), fname.c_str());
|
2008-07-30 15:38:42 +00:00
|
|
|
|
2021-10-19 09:45:46 +00:00
|
|
|
if (filesProps.contains(key))
|
2010-11-07 17:16:01 +00:00
|
|
|
continue;
|
|
|
|
|
2020-02-26 20:16:14 +00:00
|
|
|
FileProperties tmp;
|
2020-09-12 05:00:53 +00:00
|
|
|
if (getFileProperties(allFiles, *g, fname, tmp)) {
|
2021-10-21 09:51:17 +00:00
|
|
|
debugC(3, kDebugGlobalDetection, "> '%s': '%s' %ld", key.c_str(), tmp.md5.c_str(), long(tmp.size));
|
2008-11-15 13:19:40 +00:00
|
|
|
}
|
2020-02-26 20:16:14 +00:00
|
|
|
|
|
|
|
// Both positive and negative results are cached to avoid
|
|
|
|
// repeatedly checking for files.
|
2021-10-19 09:45:46 +00:00
|
|
|
filesProps[key] = tmp;
|
2006-10-02 22:21:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-12-19 23:14:15 +00:00
|
|
|
int maxFilesMatched = 0;
|
2009-03-09 23:08:44 +00:00
|
|
|
bool gotAnyMatchesWithAllFiles = false;
|
2006-12-19 23:14:15 +00:00
|
|
|
|
2007-04-26 20:35:10 +00:00
|
|
|
// MD5 based matching
|
2008-07-30 15:16:57 +00:00
|
|
|
uint i;
|
2017-12-02 15:43:48 +00:00
|
|
|
for (i = 0, descPtr = _gameDescriptors; ((const ADGameDescription *)descPtr)->gameId != nullptr; descPtr += _descItemSize, ++i) {
|
2007-02-14 00:52:12 +00:00
|
|
|
g = (const ADGameDescription *)descPtr;
|
2006-10-02 22:21:57 +00:00
|
|
|
|
2006-12-19 23:14:15 +00:00
|
|
|
// Do not even bother to look at entries which do not have matching
|
|
|
|
// language and platform (if specified).
|
2010-08-02 08:36:33 +00:00
|
|
|
if ((language != Common::UNK_LANG && g->language != Common::UNK_LANG && g->language != language
|
|
|
|
&& !(language == Common::EN_ANY && (g->flags & ADGF_ADDENGLISH))) ||
|
2009-01-29 22:13:01 +00:00
|
|
|
(platform != Common::kPlatformUnknown && g->platform != Common::kPlatformUnknown && g->platform != platform)) {
|
2006-12-19 23:14:15 +00:00
|
|
|
continue;
|
|
|
|
}
|
2007-02-04 03:10:27 +00:00
|
|
|
|
2011-06-11 15:52:32 +00:00
|
|
|
if ((_flags & kADFlagUseExtraAsHint) && !extra.empty() && g->extra != extra)
|
2007-12-31 14:45:38 +00:00
|
|
|
continue;
|
|
|
|
|
2017-12-02 16:14:22 +00:00
|
|
|
ADDetectedGame game(g);
|
2009-03-09 23:08:44 +00:00
|
|
|
bool allFilesPresent = true;
|
2010-11-07 17:15:27 +00:00
|
|
|
int curFilesMatched = 0;
|
2009-03-09 23:08:44 +00:00
|
|
|
|
2007-04-26 20:35:10 +00:00
|
|
|
// Try to match all files for this game
|
2017-12-02 16:14:22 +00:00
|
|
|
for (fileDesc = game.desc->filesDescriptions; fileDesc->fileName; fileDesc++) {
|
2021-07-27 21:44:10 +00:00
|
|
|
Common::String tstr = Common::punycode_decodefilename(fileDesc->fileName);
|
2021-10-19 10:04:18 +00:00
|
|
|
Common::String key = Common::String::format("%c:%s", flagsToMD5Prefix(g->flags), tstr.c_str());
|
2006-10-02 22:21:57 +00:00
|
|
|
|
2021-10-19 09:45:46 +00:00
|
|
|
if (!filesProps.contains(key) || filesProps[key].size == -1) {
|
2009-03-09 23:08:44 +00:00
|
|
|
allFilesPresent = false;
|
2007-02-04 03:10:27 +00:00
|
|
|
break;
|
|
|
|
}
|
2008-07-30 15:16:57 +00:00
|
|
|
|
2021-10-19 09:45:46 +00:00
|
|
|
game.matchedFiles[tstr] = filesProps[key];
|
2017-12-02 16:14:22 +00:00
|
|
|
|
|
|
|
if (game.hasUnknownFiles)
|
2017-10-08 04:06:00 +00:00
|
|
|
continue;
|
|
|
|
|
2021-10-19 10:04:18 +00:00
|
|
|
if (fileDesc->md5 != nullptr && fileDesc->md5 != filesProps[key].md5) {
|
2021-10-19 09:45:46 +00:00
|
|
|
debugC(3, kDebugGlobalDetection, "MD5 Mismatch. Skipping (%s) (%s)", fileDesc->md5, filesProps[key].md5.c_str());
|
2017-12-02 16:14:22 +00:00
|
|
|
game.hasUnknownFiles = true;
|
2017-10-08 04:06:00 +00:00
|
|
|
continue;
|
2006-10-02 22:21:57 +00:00
|
|
|
}
|
2007-02-04 03:10:27 +00:00
|
|
|
|
2021-10-19 10:04:18 +00:00
|
|
|
if (fileDesc->fileSize != -1 && fileDesc->fileSize != filesProps[key].size) {
|
2021-10-21 09:51:17 +00:00
|
|
|
debugC(3, kDebugGlobalDetection, "Size Mismatch. Skipping (%ld) (%ld)", long(fileDesc->fileSize), long(filesProps[key].size));
|
2017-12-02 16:14:22 +00:00
|
|
|
game.hasUnknownFiles = true;
|
2017-10-08 04:06:00 +00:00
|
|
|
continue;
|
2006-10-02 22:21:57 +00:00
|
|
|
}
|
2007-02-04 03:10:27 +00:00
|
|
|
|
2021-05-17 10:12:02 +00:00
|
|
|
debugC(3, kDebugGlobalDetection, "Matched file: %s", tstr.c_str());
|
2010-11-07 17:15:27 +00:00
|
|
|
curFilesMatched++;
|
2006-10-02 22:21:57 +00:00
|
|
|
}
|
2009-03-09 23:08:44 +00:00
|
|
|
|
|
|
|
// We found at least one entry with all required files present.
|
|
|
|
// That means that we got new variant of the game.
|
|
|
|
//
|
2010-11-07 17:15:27 +00:00
|
|
|
// Without this check we would have erroneous checksum display
|
2009-03-09 23:08:44 +00:00
|
|
|
// where only located files will be enlisted.
|
|
|
|
//
|
|
|
|
// Potentially this could rule out variants where some particular file
|
|
|
|
// is really missing, but the developers should better know about such
|
|
|
|
// cases.
|
2017-12-02 16:14:22 +00:00
|
|
|
if (allFilesPresent && !gotAnyMatchesWithAllFiles) {
|
2021-11-27 19:26:17 +00:00
|
|
|
// Do sanity check
|
2021-11-28 11:54:59 +00:00
|
|
|
if (game.hasUnknownFiles && isEntryGrayListed(g)) {
|
2021-11-27 19:26:17 +00:00
|
|
|
debugC(3, kDebugGlobalDetection, "Skipping game: %s (%s %s/%s) (%d), didn't pass sanity", g->gameId, g->extra,
|
|
|
|
getPlatformDescription(g->platform), getLanguageDescription(g->language), i);
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-12-02 16:14:22 +00:00
|
|
|
if (matched.empty() || strcmp(matched.back().desc->gameId, g->gameId) != 0)
|
|
|
|
matched.push_back(game);
|
2017-10-08 04:18:57 +00:00
|
|
|
}
|
2009-03-09 23:08:44 +00:00
|
|
|
|
2017-12-02 16:14:22 +00:00
|
|
|
if (allFilesPresent && !game.hasUnknownFiles) {
|
2021-05-17 10:12:02 +00:00
|
|
|
debugC(2, kDebugGlobalDetection, "Found game: %s (%s %s/%s) (%d)", g->gameId, g->extra,
|
2007-01-25 01:01:01 +00:00
|
|
|
getPlatformDescription(g->platform), getLanguageDescription(g->language), i);
|
2006-12-19 23:14:15 +00:00
|
|
|
|
|
|
|
if (curFilesMatched > maxFilesMatched) {
|
2021-05-17 10:12:02 +00:00
|
|
|
debugC(2, kDebugGlobalDetection, " ... new best match, removing all previous candidates");
|
2006-12-19 23:14:15 +00:00
|
|
|
maxFilesMatched = curFilesMatched;
|
2008-06-02 18:19:58 +00:00
|
|
|
|
2010-11-07 17:15:27 +00:00
|
|
|
matched.clear(); // Remove any prior, lower ranked matches.
|
2017-12-02 16:14:22 +00:00
|
|
|
matched.push_back(game);
|
2006-12-19 23:14:15 +00:00
|
|
|
} else if (curFilesMatched == maxFilesMatched) {
|
2017-12-02 16:14:22 +00:00
|
|
|
matched.push_back(game);
|
2006-12-19 23:14:15 +00:00
|
|
|
} else {
|
2021-05-17 10:12:02 +00:00
|
|
|
debugC(2, kDebugGlobalDetection, " ... skipped");
|
2006-12-19 23:14:15 +00:00
|
|
|
}
|
|
|
|
|
2017-12-02 16:14:22 +00:00
|
|
|
gotAnyMatchesWithAllFiles = true;
|
2006-12-19 03:52:04 +00:00
|
|
|
} else {
|
2021-05-17 10:12:02 +00:00
|
|
|
debugC(5, kDebugGlobalDetection, "Skipping game: %s (%s %s/%s) (%d)", g->gameId, g->extra,
|
2007-01-25 01:01:01 +00:00
|
|
|
getPlatformDescription(g->platform), getLanguageDescription(g->language), i);
|
2006-10-02 22:21:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-06 15:50:25 +00:00
|
|
|
debugC(2, "Totally found %d matches", matched.size());
|
|
|
|
|
2008-07-30 15:16:57 +00:00
|
|
|
return matched;
|
|
|
|
}
|
|
|
|
|
2020-10-11 21:14:39 +00:00
|
|
|
ADDetectedGame AdvancedMetaEngineDetection::detectGameFilebased(const FileMap &allFiles, const ADFileBasedFallback *fileBasedFallback) const {
|
2008-07-30 15:16:57 +00:00
|
|
|
const ADFileBasedFallback *ptr;
|
|
|
|
const char* const* filenames;
|
|
|
|
|
|
|
|
int maxNumMatchedFiles = 0;
|
2017-12-02 16:14:22 +00:00
|
|
|
ADDetectedGame result;
|
2007-02-04 03:10:27 +00:00
|
|
|
|
2011-06-14 16:11:14 +00:00
|
|
|
for (ptr = fileBasedFallback; ptr->desc; ++ptr) {
|
2011-06-14 16:21:44 +00:00
|
|
|
const ADGameDescription *agdesc = ptr->desc;
|
2008-07-30 15:16:57 +00:00
|
|
|
int numMatchedFiles = 0;
|
|
|
|
bool fileMissing = false;
|
2007-02-04 03:10:27 +00:00
|
|
|
|
2008-07-30 15:16:57 +00:00
|
|
|
for (filenames = ptr->filenames; *filenames; ++filenames) {
|
2021-05-17 10:12:02 +00:00
|
|
|
debugC(3, kDebugGlobalDetection, "++ %s", *filenames);
|
2008-07-30 15:16:57 +00:00
|
|
|
if (!allFiles.contains(*filenames)) {
|
|
|
|
fileMissing = true;
|
2008-07-30 15:48:16 +00:00
|
|
|
break;
|
2007-02-04 03:10:27 +00:00
|
|
|
}
|
|
|
|
|
2008-07-30 15:16:57 +00:00
|
|
|
numMatchedFiles++;
|
|
|
|
}
|
2007-02-04 03:10:27 +00:00
|
|
|
|
2008-07-30 15:48:16 +00:00
|
|
|
if (!fileMissing) {
|
2021-05-17 10:12:02 +00:00
|
|
|
debugC(4, kDebugGlobalDetection, "Matched: %s", agdesc->gameId);
|
2008-12-22 11:22:15 +00:00
|
|
|
|
2008-07-30 15:48:16 +00:00
|
|
|
if (numMatchedFiles > maxNumMatchedFiles) {
|
|
|
|
maxNumMatchedFiles = numMatchedFiles;
|
2008-12-22 11:22:15 +00:00
|
|
|
|
2021-05-17 10:12:02 +00:00
|
|
|
debugC(4, kDebugGlobalDetection, "and overridden");
|
2012-06-27 02:42:36 +00:00
|
|
|
|
2017-12-02 16:14:22 +00:00
|
|
|
ADDetectedGame game(agdesc);
|
|
|
|
game.hasUnknownFiles = true;
|
|
|
|
|
|
|
|
for (filenames = ptr->filenames; *filenames; ++filenames) {
|
|
|
|
FileProperties tmp;
|
2012-06-27 02:42:36 +00:00
|
|
|
|
2020-09-12 05:00:53 +00:00
|
|
|
if (getFileProperties(allFiles, *agdesc, *filenames, tmp))
|
2017-12-02 16:14:22 +00:00
|
|
|
game.matchedFiles[*filenames] = tmp;
|
2012-06-27 02:42:36 +00:00
|
|
|
}
|
|
|
|
|
2017-12-02 16:14:22 +00:00
|
|
|
result = game;
|
2008-07-30 15:48:16 +00:00
|
|
|
}
|
2007-02-04 03:10:27 +00:00
|
|
|
}
|
2008-07-30 15:16:57 +00:00
|
|
|
}
|
2007-02-04 03:10:27 +00:00
|
|
|
|
2017-12-02 16:14:22 +00:00
|
|
|
return result;
|
2006-10-02 22:21:57 +00:00
|
|
|
}
|
|
|
|
|
2020-10-11 21:14:39 +00:00
|
|
|
PlainGameList AdvancedMetaEngineDetection::getSupportedGames() const {
|
2018-05-06 11:09:52 +00:00
|
|
|
return PlainGameList(_gameIds);
|
2008-02-03 18:37:41 +00:00
|
|
|
}
|
2011-06-10 20:13:51 +00:00
|
|
|
|
2020-10-11 21:14:39 +00:00
|
|
|
PlainGameDescriptor AdvancedMetaEngineDetection::findGame(const char *gameId) const {
|
2011-06-14 13:25:33 +00:00
|
|
|
// First search the list of supported gameids for a match.
|
2016-03-08 17:30:58 +00:00
|
|
|
const PlainGameDescriptor *g = findPlainGameDescriptor(gameId, _gameIds);
|
2011-06-14 13:25:33 +00:00
|
|
|
if (g)
|
2018-05-06 10:57:08 +00:00
|
|
|
return *g;
|
2011-06-14 13:25:33 +00:00
|
|
|
|
|
|
|
// No match found
|
2018-05-10 07:26:26 +00:00
|
|
|
return PlainGameDescriptor::empty();
|
2011-06-10 13:46:36 +00:00
|
|
|
}
|
|
|
|
|
2021-11-28 11:54:59 +00:00
|
|
|
static const char *grayList[] = {
|
2021-11-27 19:26:17 +00:00
|
|
|
"game.exe",
|
|
|
|
"demo.exe",
|
|
|
|
"game",
|
|
|
|
"demo",
|
|
|
|
"data.z",
|
|
|
|
"data1.cab",
|
|
|
|
"data.cab",
|
|
|
|
"engine.exe",
|
|
|
|
"install.exe",
|
|
|
|
"play.exe",
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
2020-10-11 21:14:39 +00:00
|
|
|
AdvancedMetaEngineDetection::AdvancedMetaEngineDetection(const void *descs, uint descItemSize, const PlainGameDescriptor *gameIds, const ADExtraGuiOptionsMap *extraGuiOptions)
|
2016-03-08 17:30:58 +00:00
|
|
|
: _gameDescriptors((const byte *)descs), _descItemSize(descItemSize), _gameIds(gameIds),
|
2012-02-29 15:48:25 +00:00
|
|
|
_extraGuiOptions(extraGuiOptions) {
|
2011-06-11 15:52:32 +00:00
|
|
|
|
|
|
|
_md5Bytes = 5000;
|
|
|
|
_flags = 0;
|
2016-03-08 17:30:58 +00:00
|
|
|
_guiOptions = GUIO_NONE;
|
2011-06-11 15:52:32 +00:00
|
|
|
_maxScanDepth = 1;
|
|
|
|
_directoryGlobs = NULL;
|
2020-10-21 17:13:12 +00:00
|
|
|
_maxAutogenLength = 15;
|
2021-11-27 19:26:17 +00:00
|
|
|
|
2021-11-29 00:21:20 +00:00
|
|
|
_hashMapsInited = false;
|
|
|
|
|
2021-11-28 11:54:59 +00:00
|
|
|
for (auto f = grayList; *f; f++)
|
|
|
|
_grayListMap.setVal(*f, true);
|
2008-02-03 18:37:41 +00:00
|
|
|
}
|
2013-05-16 21:18:09 +00:00
|
|
|
|
2020-10-11 21:14:39 +00:00
|
|
|
void AdvancedMetaEngineDetection::initSubSystems(const ADGameDescription *gameDesc) const {
|
2013-07-07 03:54:45 +00:00
|
|
|
#ifdef ENABLE_EVENTRECORDER
|
2013-05-16 21:18:09 +00:00
|
|
|
if (gameDesc) {
|
|
|
|
g_eventRec.processGameDescription(gameDesc);
|
|
|
|
}
|
2013-07-07 03:54:45 +00:00
|
|
|
#endif
|
2013-05-16 21:18:09 +00:00
|
|
|
}
|
2020-08-02 15:07:48 +00:00
|
|
|
|
2021-11-29 00:21:20 +00:00
|
|
|
void AdvancedMetaEngineDetection::preprocessDescriptions() {
|
|
|
|
if (_hashMapsInited)
|
|
|
|
return;
|
|
|
|
|
|
|
|
_hashMapsInited = true;
|
|
|
|
|
2021-11-29 00:49:18 +00:00
|
|
|
// Put all directory globs into a hashmap for faster usage
|
|
|
|
if (_directoryGlobs) {
|
|
|
|
for (auto glob = _directoryGlobs; *glob; glob++)
|
|
|
|
_globsMap.setVal(*glob, true);
|
|
|
|
}
|
|
|
|
|
2021-11-30 22:46:41 +00:00
|
|
|
// Now scan all detection entries
|
2021-11-27 19:26:17 +00:00
|
|
|
for (const byte *descPtr = _gameDescriptors; ((const ADGameDescription *)descPtr)->gameId != nullptr; descPtr += _descItemSize) {
|
|
|
|
const ADGameDescription *g = (const ADGameDescription *)descPtr;
|
|
|
|
|
2021-11-30 22:46:41 +00:00
|
|
|
// Scan for potential directory globs
|
|
|
|
for (const ADGameFileDescription *fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) {
|
|
|
|
if (strchr(fileDesc->fileName, '/')) {
|
2021-11-30 22:58:48 +00:00
|
|
|
if (!(_flags & kADFlagMatchFullPaths))
|
|
|
|
warning("Path component detected in entry for '%s' in engine '%s' but no kADFlagMatchFullPaths is set",
|
2021-11-30 22:46:41 +00:00
|
|
|
g->gameId, getEngineId());
|
|
|
|
|
|
|
|
Common::StringTokenizer tok(fileDesc->fileName, "/");
|
|
|
|
|
|
|
|
while (!tok.empty()) {
|
|
|
|
Common::String component = tok.nextToken();
|
|
|
|
|
|
|
|
if (!tok.empty()) { // If it is not the last component
|
|
|
|
_globsMap.setVal(component, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the detection entry have only files from the blacklist
|
2021-11-28 11:54:59 +00:00
|
|
|
if (isEntryGrayListed(g)) {
|
2021-11-27 19:26:17 +00:00
|
|
|
debug(0, "WARNING: Detection entry for '%s' in engine '%s' contains only blacklisted names. Add more files to the entry (%s)",
|
|
|
|
g->gameId, getEngineId(), g->filesDescriptions[0].md5);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-30 22:46:41 +00:00
|
|
|
Common::StringArray AdvancedMetaEngineDetection::getPathsFromEntry(const ADGameDescription *g) {
|
|
|
|
Common::StringArray result;
|
|
|
|
|
|
|
|
for (const ADGameFileDescription *fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) {
|
|
|
|
if (!strchr(fileDesc->fileName, '/'))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
Common::StringTokenizer tok(fileDesc->fileName, "/");
|
|
|
|
|
|
|
|
while (!tok.empty()) {
|
|
|
|
Common::String component = tok.nextToken();
|
|
|
|
|
|
|
|
if (!tok.empty()) { // If it is not the last component
|
|
|
|
result.push_back(component);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-11-28 11:54:59 +00:00
|
|
|
bool AdvancedMetaEngineDetection::isEntryGrayListed(const ADGameDescription *g) const {
|
|
|
|
bool grayIsPresent = false, nonGrayIsPresent = false;
|
2021-11-27 19:26:17 +00:00
|
|
|
|
|
|
|
for (const ADGameFileDescription *fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) {
|
2021-11-28 11:54:59 +00:00
|
|
|
if (_grayListMap.contains(fileDesc->fileName)) {
|
|
|
|
grayIsPresent = true;
|
2021-11-27 19:26:17 +00:00
|
|
|
} else {
|
2021-11-28 11:54:59 +00:00
|
|
|
nonGrayIsPresent = true;
|
2021-11-27 19:26:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-28 11:54:59 +00:00
|
|
|
return (grayIsPresent && !nonGrayIsPresent);
|
2021-11-27 19:26:17 +00:00
|
|
|
}
|
|
|
|
|
2021-11-29 00:21:20 +00:00
|
|
|
Common::Error AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) {
|
2020-08-02 15:07:48 +00:00
|
|
|
PluginList pl = PluginMan.getPlugins(PLUGIN_TYPE_ENGINE);
|
|
|
|
if (pl.size() == 1) {
|
2021-08-16 18:29:49 +00:00
|
|
|
const Plugin *metaEnginePlugin = PluginMan.getMetaEngineFromEngine(pl[0]);
|
2020-08-02 15:07:48 +00:00
|
|
|
if (metaEnginePlugin) {
|
2020-10-11 21:14:39 +00:00
|
|
|
return metaEnginePlugin->get<AdvancedMetaEngineDetection>().createInstance(syst, engine);
|
2020-08-02 15:07:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Common::Error();
|
|
|
|
}
|