GameList AdvancedMetaEngine::detectGames(const Common::FSList &fslist) const {
	ADGameDescList matches;
	GameList detectedGames;
	FileMap allFiles;

	if (fslist.empty())
		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, "");

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

	return detectedGames;
}
Exemple #2
0
static void composeFileHashMap(const Common::FSList &fslist, DescMap &fileMD5Map, int depth, const char **globs) {
	if (depth <= 0)
		return;

	if (fslist.empty())
		return;

	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
		if (!file->isDirectory()) {
			DetectorDesc d;
			d.node = *file;
			d.md5Entry = 0;
			fileMD5Map[file->getName()] = d;
		} else {
			if (!globs)
				continue;

			bool matched = false;
			for (const char **glob = globs; *glob; glob++)
				if (file->getName().matchString(*glob, true)) {
					matched = true;
					break;
				}
					
			if (!matched)
				continue;

			Common::FSList files;

			if (file->getChildren(files, Common::FSNode::kListAll)) {
				composeFileHashMap(files, fileMD5Map, depth - 1, globs);
			}
		}
	}
}
static void composeFileHashMap(const Common::FSList &fslist, FileMap &allFiles, int depth, const char * const *directoryGlobs) {
    if (depth <= 0)
        return;

    if (fslist.empty())
        return;

    // First we compose a hashmap of all files in fslist.
    // Includes nifty stuff like removing trailing dots and ignoring case.
    for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
        if (file->isDirectory()) {
            Common::FSList files;

            if (!directoryGlobs)
                continue;

            bool matched = false;
            for (const char * const *glob = directoryGlobs; *glob; glob++)
                if (file->getName().matchString(*glob, true)) {
                    matched = true;
                    break;
                }

            if (!matched)
                continue;

            if (!file->getChildren(files, Common::FSNode::kListAll))
                continue;

            composeFileHashMap(files, allFiles, depth - 1, directoryGlobs);
        }

        Common::String tstr = file->getName();

        // Strip any trailing dot
        if (tstr.lastChar() == '.')
            tstr.deleteLastChar();

        allFiles[tstr] = *file;	// Record the presence of this file
    }
}
void AdvancedMetaEngine::composeFileHashMap(FileMap &allFiles, const Common::FSList &fslist, int depth, const Common::String &parentName) const {
	if (depth <= 0)
		return;

	if (fslist.empty())
		return;

	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
		Common::String tstr = (_matchFullPaths && !parentName.empty() ? parentName + "/" : "") + file->getName();

		if (file->isDirectory()) {
			Common::FSList files;

			if (!_directoryGlobs)
				continue;

			bool matched = false;
			for (const char * const *glob = _directoryGlobs; *glob; glob++)
				if (file->getName().matchString(*glob, true)) {
					matched = true;
					break;
				}

			if (!matched)
				continue;

			if (!file->getChildren(files, Common::FSNode::kListAll))
				continue;

			composeFileHashMap(allFiles, files, depth - 1, tstr);
		}

		// Strip any trailing dot
		if (tstr.lastChar() == '.')
			tstr.deleteLastChar();

		allFiles[tstr] = *file;	// Record the presence of this file
	}
}
Exemple #5
0
Common::HashMap<Common::String, Score *> DirectorEngine::loadMMMNames(Common::String folder) {
	Common::FSNode directory(folder);
	Common::FSList movies;

	Common::HashMap<Common::String, Score *> nameMap;
	directory.getChildren(movies, Common::FSNode::kListFilesOnly);

	if (!movies.empty()) {
		for (Common::FSList::const_iterator i = movies.begin(); i != movies.end(); ++i) {
			if (i->getName() == _sharedMMM) {
				loadSharedCastsFrom(i->getPath());
				continue;
			}

			RIFFArchive *arc = new RIFFArchive();
			arc->openFile(i->getPath());
			Score *sc = new Score(this);
			nameMap[sc->getMacName()] = sc;
		}
	}

	return nameMap;
}
Exemple #6
0
Common::HashMap<Common::String, Score *> *DirectorEngine::scanMovies(const Common::String &folder) {
    Common::FSNode directory(folder);
    Common::FSList movies;
    const char *sharedMMMname;

    if (getPlatform() == Common::kPlatformWindows)
        sharedMMMname = "SHARDCST.MMM";
    else
        sharedMMMname = "Shared Cast*";


    Common::HashMap<Common::String, Score *> *nameMap = new Common::HashMap<Common::String, Score *>();
    if (!directory.getChildren(movies, Common::FSNode::kListFilesOnly))
        return nameMap;

    if (!movies.empty()) {
        for (Common::FSList::const_iterator i = movies.begin(); i != movies.end(); ++i) {
            debugC(2, kDebugLoading, "File: %s", i->getName().c_str());

            if (Common::matchString(i->getName().c_str(), sharedMMMname, true)) {
                _sharedCastFile = i->getName();
                continue;
            }

            Archive *arc = createArchive();

            warning("name: %s", i->getName().c_str());
            arc->openFile(i->getName());
            Score *sc = new Score(this, arc);
            nameMap->setVal(sc->getMacName(), sc);

            debugC(2, kDebugLoading, "Movie name: \"%s\"", sc->getMacName().c_str());
        }
    }

    return nameMap;
}
static ADGameDescList detectGame(const Common::FSList &fslist, const ADParams &params, Common::Language language, Common::Platform platform, const Common::String &extra) {
    FileMap allFiles;
    SizeMD5Map filesSizeMD5;

    const ADGameFileDescription *fileDesc;
    const ADGameDescription *g;
    const byte *descPtr;

    if (fslist.empty())
        return ADGameDescList();
    Common::FSNode parent = fslist.begin()->getParent();
    debug(3, "Starting detection in dir '%s'", parent.getPath().c_str());

    // First we compose a hashmap of all files in fslist.
    // Includes nifty stuff like removing trailing dots and ignoring case.
    composeFileHashMap(fslist, allFiles, (params.depth == 0 ? 1 : params.depth), params.directoryGlobs);

    // Check which files are included in some ADGameDescription *and* present
    // in fslist. Compute MD5s and file sizes for these files.
    for (descPtr = params.descs; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += params.descItemSize) {
        g = (const ADGameDescription *)descPtr;

        for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) {
            Common::String fname = fileDesc->fileName;
            SizeMD5 tmp;

            if (filesSizeMD5.contains(fname))
                continue;

            // FIXME/TODO: We don't handle the case that a file is listed as a regular
            // file and as one with resource fork.

            if (g->flags & ADGF_MACRESFORK) {
                Common::MacResManager *macResMan = new Common::MacResManager();

                if (macResMan->open(parent, fname)) {
                    tmp.md5 = macResMan->computeResForkMD5AsString(params.md5Bytes);
                    tmp.size = macResMan->getResForkDataSize();
                    debug(3, "> '%s': '%s'", fname.c_str(), tmp.md5.c_str());
                    filesSizeMD5[fname] = tmp;
                }

                delete macResMan;
            } else {
                if (allFiles.contains(fname)) {
                    debug(3, "+ %s", fname.c_str());

                    Common::File testFile;

                    if (testFile.open(allFiles[fname])) {
                        tmp.size = (int32)testFile.size();
                        tmp.md5 = Common::computeStreamMD5AsString(testFile, params.md5Bytes);
                    } else {
                        tmp.size = -1;
                    }

                    debug(3, "> '%s': '%s'", fname.c_str(), tmp.md5.c_str());
                    filesSizeMD5[fname] = tmp;
                }
            }
        }
    }

    ADGameDescList matched;
    int maxFilesMatched = 0;
    bool gotAnyMatchesWithAllFiles = false;

    // MD5 based matching
    uint i;
    for (i = 0, descPtr = params.descs; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += params.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).
        if ((language != Common::UNK_LANG && g->language != Common::UNK_LANG && g->language != language
                && !(language == Common::EN_ANY && (g->flags & ADGF_ADDENGLISH))) ||
                (platform != Common::kPlatformUnknown && g->platform != Common::kPlatformUnknown && g->platform != platform)) {
            continue;
        }

        if ((params.flags & kADFlagUseExtraAsHint) && !extra.empty() && g->extra != extra)
            continue;

        bool allFilesPresent = true;
        int curFilesMatched = 0;

        // Try to match all files for this game
        for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) {
            Common::String tstr = fileDesc->fileName;

            if (!filesSizeMD5.contains(tstr)) {
                fileMissing = true;
                allFilesPresent = false;
                break;
            }

            if (fileDesc->md5 != NULL && fileDesc->md5 != filesSizeMD5[tstr].md5) {
                debug(3, "MD5 Mismatch. Skipping (%s) (%s)", fileDesc->md5, filesSizeMD5[tstr].md5.c_str());
                fileMissing = true;
                break;
            }

            if (fileDesc->fileSize != -1 && fileDesc->fileSize != filesSizeMD5[tstr].size) {
                debug(3, "Size Mismatch. Skipping");
                fileMissing = true;
                break;
            }

            debug(3, "Matched file: %s", tstr.c_str());
            curFilesMatched++;
        }

        // We found at least one entry with all required files present.
        // That means that we got new variant of the game.
        //
        // Without this check we would have erroneous checksum display
        // 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.
        if (allFilesPresent)
            gotAnyMatchesWithAllFiles = true;

        if (!fileMissing) {
            debug(2, "Found game: %s (%s %s/%s) (%d)", g->gameid, g->extra,
                  getPlatformDescription(g->platform), getLanguageDescription(g->language), i);

            if (curFilesMatched > maxFilesMatched) {
                debug(2, " ... new best match, removing all previous candidates");
                maxFilesMatched = curFilesMatched;

                matched.clear();	// Remove any prior, lower ranked matches.
                matched.push_back(g);
            } else if (curFilesMatched == maxFilesMatched) {
                matched.push_back(g);
            } else {
                debug(2, " ... skipped");
            }

        } 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 (!filesSizeMD5.empty() && gotAnyMatchesWithAllFiles) {
            reportUnknown(parent, filesSizeMD5);
        }

        // Filename based fallback
        if (params.fileBasedFallback != 0)
            matched = detectGameFilebased(allFiles, params);
    }

    return matched;
}
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;

	if (ConfMan.hasKey("language"))
		language = Common::parseLanguage(ConfMan.get("language"));
	if (ConfMan.hasKey("platform"))
		platform = Common::parsePlatform(ConfMan.get("platform"));
	if (_flags & kADFlagUseExtraAsHint) {
		if (ConfMan.hasKey("extra"))
			extra = ConfMan.get("extra");
	}

	Common::String gameid = ConfMan.get("gameid");

	Common::String path;
	if (ConfMan.hasKey("path")) {
		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);
	Common::FSList files;
	if (!dir.isDirectory() || !dir.getChildren(files, Common::FSNode::kListAll, true)) {
		warning("Game data path does not exist or is not a directory (%s)", path.c_str());
		return Common::kNoGameDataFoundError;
	}

	if (files.empty())
		return Common::kNoGameDataFoundError;

	// Compose a hashmap of all files in fslist.
	FileMap allFiles;
	composeFileHashMap(allFiles, files, (_maxScanDepth == 0 ? 1 : _maxScanDepth));

	// Run the detector on this
	ADGameDescList 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;
			}
		}
	} else if (matches.size() > 0) {
		agdDesc = matches[0];
	}

	if (agdDesc == 0) {
		// Use fallback detector if there were no matches by other means
		agdDesc = fallbackDetect(allFiles, files);
		if (agdDesc != 0) {
			// 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 (agdDesc == 0)
		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)
		lang += " " + getGameGUIOptionsDescriptionLanguage(Common::EN_ANY);

	Common::updateGameGUIOptions(agdDesc->guiOptions + _guiOptions, lang);

	GameDescriptor gameDescriptor = toGameDescriptor(*agdDesc, _gameIds);

	bool showTestingWarning = false;

#ifdef RELEASE_BUILD
	showTestingWarning = true;
#endif

	if (((gameDescriptor.getSupportLevel() == kUnstableGame
			|| (gameDescriptor.getSupportLevel() == kTestingGame
					&& showTestingWarning)))
			&& !Engine::warnUserAboutUnsupportedGame())
		return Common::kUserCanceled;

	debug(2, "Running %s", gameDescriptor.description().c_str());
	initSubSystems(agdDesc);
	if (!createInstance(syst, engine, agdDesc))
		return Common::kNoGameDataFoundError;
	else
		return Common::kNoError;
}