Exemple #1
0
void ThemeEngine::listUsableThemes(const Common::FSNode &node, Common::List<ThemeDescriptor> &list, int depth) {
	if (!node.exists() || !node.isReadable() || !node.isDirectory())
		return;

	ThemeDescriptor td;

	// Check whether we point to a valid theme directory.
	if (themeConfigUsable(node, td.name)) {
		td.filename = node.getPath();
		td.id = node.getName();

		list.push_back(td);

		// A theme directory should never contain any other themes
		// thus we just return to the caller here.
		return;
	}

	Common::FSList fileList;
	// Check all files. We need this to find all themes inside ZIP archives.
	if (!node.getChildren(fileList, Common::FSNode::kListFilesOnly))
		return;

	for (Common::FSList::iterator i = fileList.begin(); i != fileList.end(); ++i) {
		// We will only process zip files for now
		if (!i->getPath().matchString("*.zip", true))
			continue;

		td.name.clear();
		if (themeConfigUsable(*i, td.name)) {
			td.filename = i->getPath();
			td.id = i->getName();

			// If the name of the node object also contains
			// the ".zip" suffix, we will strip it.
			if (td.id.matchString("*.zip", true)) {
				for (int j = 0; j < 4; ++j)
					td.id.deleteLastChar();
			}

			list.push_back(td);
		}
	}

	fileList.clear();

	// Check if we exceeded the given recursion depth
	if (depth - 1 == -1)
		return;

	// As next step we will search all subdirectories
	if (!node.getChildren(fileList, Common::FSNode::kListDirectoriesOnly))
		return;

	for (Common::FSList::iterator i = fileList.begin(); i != fileList.end(); ++i)
		listUsableThemes(*i, list, depth == -1 ? - 1 : depth - 1);
}
Exemple #2
0
	virtual ADDetectedGame fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const override {
		for (Common::FSList::const_iterator d = fslist.begin(); d != fslist.end(); ++d) {
			Common::FSList audiofslist;
			if (d->isDirectory() && d->getName().equalsIgnoreCase("audio") && d->getChildren(audiofslist, Common::FSNode::kListFilesOnly)) {
				for (Common::FSList::const_iterator f = audiofslist.begin(); f != audiofslist.end(); ++f) {
					if (!f->isDirectory() && f->getName().equalsIgnoreCase("demorolc.raw")) {
						return ADDetectedGame(&tuckerDemoGameDescription);
					}
				}
			}
		}

		return ADDetectedGame();
	}
Exemple #3
0
GameList Sword2MetaEngine::detectGames(const Common::FSList &fslist) const {
	GameList detectedGames;
	const Sword2::GameSettings *g;
	Common::FSList::const_iterator file;

	// TODO: It would be nice if we had code here which distinguishes
	// between the 'sword2' and 'sword2demo' targets. The current code
	// can't do that since they use the same detectname.

	for (g = Sword2::sword2_settings; g->gameid; ++g) {
		// Iterate over all files in the given directory
		for (file = fslist.begin(); file != fslist.end(); ++file) {
			if (!file->isDirectory()) {
				const char *fileName = file->getName().c_str();

				if (0 == scumm_stricmp(g->detectname, fileName)) {
					// Match found, add to list of candidates, then abort inner loop.
					detectedGames.push_back(GameDescriptor(g->gameid, g->description, Common::UNK_LANG, Common::kPlatformUnknown, Common::GUIO_NOMIDI));
					break;
				}
			}
		}
	}


	if (detectedGames.empty()) {
		// Nothing found -- try to recurse into the 'clusters' subdirectory,
		// present e.g. if the user copied the data straight from CD.
		for (file = fslist.begin(); file != fslist.end(); ++file) {
			if (file->isDirectory()) {
				const char *fileName = file->getName().c_str();

				if (0 == scumm_stricmp("clusters", fileName)) {
					Common::FSList recList;
					if (file->getChildren(recList, Common::FSNode::kListAll)) {
						GameList recGames(detectGames(recList));
						if (!recGames.empty()) {
							detectedGames.push_back(recGames);
							break;
						}
					}
				}
			}
		}
	}


	return detectedGames;
}
Exemple #4
0
GameList QueenMetaEngine::detectGames(const Common::FSList &fslist) const {
	GameList detectedGames;

	// Iterate over all files in the given directory
	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
		if (file->isDirectory()) {
			continue;
		}
		if (file->getName().equalsIgnoreCase("queen.1") || file->getName().equalsIgnoreCase("queen.1c")) {
			Common::File dataFile;
			if (!dataFile.open(*file)) {
				continue;
			}
			Queen::DetectedGameVersion version;
			if (Queen::Resource::detectVersion(&version, &dataFile)) {
				GameDescriptor dg(queenGameDescriptor.gameid, queenGameDescriptor.description, version.language, version.platform);
				if (version.features & Queen::GF_DEMO) {
					dg.updateDesc("Demo");
					dg.setGUIOptions(Common::GUIO_NOSPEECH);
				} else if (version.features & Queen::GF_INTERVIEW) {
					dg.updateDesc("Interview");
					dg.setGUIOptions(Common::GUIO_NOSPEECH);
				} else if (version.features & Queen::GF_FLOPPY) {
					dg.updateDesc("Floppy");
					dg.setGUIOptions(Common::GUIO_NOSPEECH);
				} else if (version.features & Queen::GF_TALKIE) {
					dg.updateDesc("Talkie");
				}
				detectedGames.push_back(dg);
				break;
			}
		}
	}
	return detectedGames;
}
Exemple #5
0
bool GlulxeMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) {
	const char *const EXTENSIONS[3] = { ".ulx", ".blb", ".gblorb" };

	// Loop through the files of the folder
	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
		// Check for a recognised filename
		if (file->isDirectory())
			continue;
		Common::String filename = file->getName();
		bool hasExt = false;
		for (int idx = 0; idx < 3 && !hasExt; ++idx)
			hasExt = filename.hasSuffixIgnoreCase(EXTENSIONS[idx]);
		if (!hasExt)
			continue;

		// Open up the file and calculate the md5
		Common::File gameFile;
		if (!gameFile.open(*file))
			continue;
		Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
		size_t filesize = gameFile.size();
		gameFile.close();

		// Check for known games
		const GlulxeGameDescription *p = GLULXE_GAMES;
		while (p->_gameId && (md5 != p->_md5 || filesize != p->_filesize))
			++p;

		DetectedGame gd;
		if (!p->_gameId) {
			if (filename.hasSuffixIgnoreCase(".blb"))
				continue;

			if (gDebugLevel > 0) {
				// Print an entry suitable for putting into the detection_tables.h, using the
				// name of the parent folder the game is in as the presumed game Id
				Common::String folderName = file->getParent().getName();
				if (folderName.hasSuffix("\\"))
					folderName.deleteLastChar();
				Common::String fname = filename;
				const char *dot = strchr(fname.c_str(), '.');
				if (dot)
					fname = Common::String(fname.c_str(), dot);

				debug("ENTRY0(\"%s\", \"%s\", %u),", fname.c_str(), md5.c_str(), (uint)filesize);
			}
			const PlainGameDescriptor &desc = GLULXE_GAME_LIST[0];
			gd = DetectedGame(desc.gameId, desc.description, Common::UNK_LANG, Common::kPlatformUnknown);
		} else {
			PlainGameDescriptor gameDesc = findGame(p->_gameId);
			gd = DetectedGame(p->_gameId, gameDesc.description, p->_language, Common::kPlatformUnknown, p->_extra);
			gd.setGUIOptions(GUIO4(GUIO_NOSPEECH, GUIO_NOSFX, GUIO_NOMUSIC, GUIO_NOSUBTITLES));
		}

		gd.addExtraEntry("filename", filename);
		gameList.push_back(gd);
	}

	return !gameList.empty();
}
Exemple #6
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);
			}
		}
	}
}
Exemple #7
0
void Sword1CheckDirectory(const Common::FSList &fslist, bool *filesFound, bool recursion = false) {
	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
		if (!file->isDirectory()) {
			// The required game data files can be located in the game directory, or in
			// a subdirectory called "clusters". In the latter case, we don't want to
			// detect the game in that subdirectory, as this will detect the game twice
			// when mass add is searching inside a directory. In this case, the first
			// result (the game directory) will be correct, but the second result (the
			// clusters subdirectory) will be wrong, as the optional speech, music and
			// video data files will be ignored. Note that this fix will skip the game
			// data files if the user has placed them inside a "clusters" subdirectory,
			// or if he/she points ScummVM directly to the "clusters" directory of the
			// game CD. Fixes bug #3049346.
			Common::String directory = file->getParent().getName();
			directory.toLowercase();
			if (directory.hasPrefix("clusters") && directory.size() <= 9 && !recursion)
				continue;

			const char *fileName = file->getName().c_str();
			for (int cnt = 0; cnt < NUM_FILES_TO_CHECK; cnt++)
				if (scumm_stricmp(fileName, g_filesToCheck[cnt]) == 0)
					filesFound[cnt] = true;
		} else {
			for (int cnt = 0; cnt < ARRAYSIZE(g_dirNames); cnt++)
				if (scumm_stricmp(file->getName().c_str(), g_dirNames[cnt]) == 0) {
					Common::FSList fslist2;
					if (file->getChildren(fslist2, Common::FSNode::kListFilesOnly))
						Sword1CheckDirectory(fslist2, filesFound, true);
				}
		}
	}
}
Exemple #8
0
// Search for a node with the given "name", inside fslist. Ignores case
// when performing the matching. The first match is returned, so if you
// search for "resource" and two nodes "RESOURE and "resource" are present,
// the first match is used.
static bool searchFSNode(const Common::FSList &fslist, const Common::String &name, Common::FSNode &result) {
	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
		if (!scumm_stricmp(file->getName().c_str(), name.c_str())) {
			result = *file;
			return true;
		}
	}
	return false;
}
bool BaseFileManager::registerPackages(const Common::FSList &fslist) {
	for (Common::FSList::const_iterator it = fslist.begin(); it != fslist.end(); ++it) {
		debugC(kWintermuteDebugFileAccess, "Adding %s", (*it).getName().c_str());
		if ((*it).getName().contains(".dcp")) {
			if (registerPackage((*it))) {
				addPath(PATH_PACKAGE, (*it));
			}
		}
	}
	return true;
}
Exemple #10
0
bool Sword25Engine::loadPackages() {
	PackageManager *packageManagerPtr = Kernel::getInstance()->getPackage();
	assert(packageManagerPtr);

	// Load the main package
	if (!packageManagerPtr->loadPackage("data.b25c", "/"))
		return false;

	// Get the contents of the main program directory and sort them alphabetically
	Common::FSNode dir(ConfMan.get("path"));
	Common::FSList files;
	if (!dir.isDirectory() || !dir.getChildren(files, Common::FSNode::kListAll)) {
		warning("Game data path does not exist or is not a directory");
		return false;
	}

	Common::sort(files.begin(), files.end());

	// Identify all patch packages
	// The filename of patch packages must have the form patch??.b25c, with the question marks
	// are placeholders for numbers.
	// Since the filenames have been sorted, patches are mounted with low numbers first, through
	// to ones with high numbers. This is important, because newly mount packages overwrite
	// existing files in the virtual file system, if they include files with the same name.
	for (Common::FSList::const_iterator it = files.begin(); it != files.end(); ++it) {
		if (it->getName().matchString("patch???.b25c", true))
			if (!packageManagerPtr->loadPackage(it->getName(), "/"))
				return false;
	}

	// Identify and mount all language packages
	// The filename of the packages have the form lang_*.b25c (eg. lang_de.b25c)
	for (Common::FSList::const_iterator it = files.begin(); it != files.end(); ++it) {
		if (it->getName().matchString("lang_*.b25c", true))
			if (!packageManagerPtr->loadPackage(it->getName(), "/"))
				return false;
	}

	return true;
}
Exemple #11
0
bool isFullGame(const Common::FSList &fslist) {
	Common::FSList::const_iterator file;

	// We distinguish between the two versions by the presense of paris.clu
	for (file = fslist.begin(); file != fslist.end(); ++file) {
		if (!file->isDirectory()) {
			if (file->getName().equalsIgnoreCase("paris.clu"))
				return true;
		}
	}

	return false;
}
Exemple #12
0
bool TADSMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) {
	// Loop through the files of the folder
	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
		// Check for a recognised filename
		Common::String filename = file->getName();
		if (file->isDirectory() || !(filename.hasSuffixIgnoreCase(".gam")
				|| filename.hasSuffixIgnoreCase(".blorb")))
			continue;

		// Open up the file and calculate the md5
		Common::File gameFile;
		if (!gameFile.open(*file))
			continue;
		Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
		size_t filesize = gameFile.size();
		gameFile.close();

		// Check for known games
		const TADSGameDescription *p = TADS_GAMES;
		while (p->_gameId && p->_md5 && (md5 != p->_md5 || filesize != p->_filesize))
			++p;

		DetectedGame gd;
		if (!p->_gameId) {
			if (!filename.hasSuffixIgnoreCase(".gam"))
				continue;

			if (gDebugLevel > 0) {
				// Print an entry suitable for putting into the detection_tables.h, using the
				Common::String fname = filename;
				const char *dot = strchr(fname.c_str(), '.');
				if (dot)
					fname = Common::String(fname.c_str(), dot);

				debug("ENTRY0(\"%s\", \"%s\", %u),", fname.c_str(), md5.c_str(), (uint)filesize);
			}
			const TADSDescriptor &desc = TADS_GAME_LIST[0];
			gd = DetectedGame(desc.gameId, desc.description, Common::UNK_LANG, Common::kPlatformUnknown);
		}
		else {
			PlainGameDescriptor gameDesc = findGame(p->_gameId);
			gd = DetectedGame(p->_gameId, gameDesc.description, p->_language, Common::kPlatformUnknown, p->_extra);
		}

		gd.addExtraEntry("filename", filename);
		gameList.push_back(gd);
	}

	return !gameList.empty();
}
Exemple #13
0
GameList SkyMetaEngine::detectGames(const Common::FSList &fslist) const {
	GameList detectedGames;
	bool hasSkyDsk = false;
	bool hasSkyDnr = false;
	int dinnerTableEntries = -1;
	int dataDiskSize = -1;

	// Iterate over all files in the given directory
	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
		if (!file->isDirectory()) {
			const char *fileName = file->getName().c_str();

			if (0 == scumm_stricmp("sky.dsk", fileName)) {
				Common::File dataDisk;
				if (dataDisk.open(*file)) {
					hasSkyDsk = true;
					dataDiskSize = dataDisk.size();
				}
			}

			if (0 == scumm_stricmp("sky.dnr", fileName)) {
				Common::File dinner;
				if (dinner.open(*file)) {
					hasSkyDnr = true;
					dinnerTableEntries = dinner.readUint32LE();
				}
			}
		}
	}

	if (hasSkyDsk && hasSkyDnr) {
		// Match found, add to list of candidates, then abort inner loop.
		// The game detector uses US English by default. We want British
		// English to match the recorded voices better.
		GameDescriptor dg("sky", skySetting.gameid, skySetting.description, Common::UNK_LANG, Common::kPlatformUnknown);
		const SkyVersion *sv = skyVersions;
		while (sv->dinnerTableEntries) {
			if (dinnerTableEntries == sv->dinnerTableEntries &&
				(sv->dataDiskSize == dataDiskSize || sv->dataDiskSize == -1)) {
				dg.updateDesc(Common::String::format("v0.0%d %s", sv->version, sv->extraDesc).c_str());
				dg.setGUIOptions(sv->guioptions);
				break;
			}
			++sv;
		}
		detectedGames.push_back(dg);
	}

	return detectedGames;
}
Exemple #14
0
void Sword1CheckDirectory(const Common::FSList &fslist, bool *filesFound) {
	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
		if (!file->isDirectory()) {
			const char *fileName = file->getName().c_str();
			for (int cnt = 0; cnt < NUM_FILES_TO_CHECK; cnt++)
				if (scumm_stricmp(fileName, g_filesToCheck[cnt]) == 0)
					filesFound[cnt] = true;
		} else {
			for (int cnt = 0; cnt < ARRAYSIZE(g_dirNames); cnt++)
				if (scumm_stricmp(file->getName().c_str(), g_dirNames[cnt]) == 0) {
					Common::FSList fslist2;
					if (file->getChildren(fslist2, Common::FSNode::kListFilesOnly))
						Sword1CheckDirectory(fslist2, filesFound);
				}
		}
	}
}
Exemple #15
0
void DefaultSaveFileManager::assureCached(const Common::String &savePathName) {
	// Check that path exists and is usable.
	checkPath(Common::FSNode(savePathName));

#ifdef USE_LIBCURL
	Common::Array<Common::String> files = CloudMan.getSyncingFiles(); //returns empty array if not syncing
	if (!files.empty()) updateSavefilesList(files); //makes this cache invalid
	else _lockedFiles = files;
#endif

	if (_cachedDirectory == savePathName) {
		return;
	}

	_saveFileCache.clear();
	_cachedDirectory.clear();

	if (getError().getCode() != Common::kNoError) {
		warning("DefaultSaveFileManager::assureCached: Can not cache path '%s': '%s'", savePathName.c_str(), getErrorDesc().c_str());
		return;
	}

	// FSNode can cache its members, thus create it after checkPath to reflect
	// actual file system state.
	const Common::FSNode savePath(savePathName);

	Common::FSList children;
	if (!savePath.getChildren(children, Common::FSNode::kListFilesOnly)) {
		return;
	}

	// Build the savefile name cache.
	for (Common::FSList::const_iterator file = children.begin(), end = children.end(); file != end; ++file) {
		if (_saveFileCache.contains(file->getName())) {
			warning("DefaultSaveFileManager::assureCached: Name clash when building cache, ignoring file '%s'", file->getName().c_str());
		} else {
			_saveFileCache[file->getName()] = *file;
		}
	}

	// Only now store that we cached 'savePathName' to indicate we successfully
	// cached the directory.
	_cachedDirectory = savePathName;
}
Exemple #16
0
void CELauncherDialog::automaticScanDirectory(const Common::FSNode &node) {
	// First check if we have a recognized game in the current directory
	Common::FSList files;
	node.getChildren(files, Common::FSNode::kListFilesOnly);
	// detect
	GameList candidates(EngineMan.detectGames(files));
	// insert
	if (candidates.size() >= 1) {
		GameDescriptor result = candidates[0];
		result["path"] = node.getPath();
		addGameToConf(result);
	}
	// Then recurse on the subdirectories
	Common::FSList dirs;
	node.getChildren(dirs, Common::FSNode::kListDirectoriesOnly);
	for (Common::FSList::const_iterator currentDir = dirs.begin(); currentDir != dirs.end(); ++currentDir)
		automaticScanDirectory(*currentDir);

}
Exemple #17
0
bool BaseFileManager::registerPackages() {
	debugC(kWintermuteDebugFileAccess | kWintermuteDebugLog, "Scanning packages");

	// Register without using SearchMan, as otherwise the FSNode-based lookup in openPackage will fail
	// and that has to be like that to support the detection-scheme.
	Common::FSList files;
	for (Common::FSList::iterator it = _packagePaths.begin(); it != _packagePaths.end(); ++it) {
		debugC(kWintermuteDebugFileAccess, "Should register folder: %s %s", (*it).getPath().c_str(), (*it).getName().c_str());
		if (!(*it).getChildren(files, Common::FSNode::kListFilesOnly)) {
			warning("getChildren() failed for path: %s", (*it).getDisplayName().c_str());
		}
		for (Common::FSList::iterator fileIt = files.begin(); fileIt != files.end(); ++fileIt) {
			if (!fileIt->getName().hasSuffix(".dcp")) {
				continue;
			}
			// Avoid registering all the language files
			// TODO: Select based on the gameDesc.
			if (_language != Common::UNK_LANG && fileIt->getParent().getName() == "language") {
				Common::String parentName = fileIt->getParent().getName();
				Common::String dcpName = fileIt->getName();
				if (_language == Common::EN_ANY && fileIt->getName() != "english.dcp") {
					continue;
				} else if (_language == Common::CZ_CZE && fileIt->getName() != "czech.dcp") {
					continue;
				} else if (_language == Common::IT_ITA && fileIt->getName() != "italian.dcp") {
					continue;
				} else if (_language == Common::PL_POL && fileIt->getName() != "polish.dcp") {
					continue;
				} else if (_language == Common::RU_RUS && fileIt->getName() != "russian.dcp") {
					continue;
				}
			}
			debugC(kWintermuteDebugFileAccess, "Registering %s %s", (*fileIt).getPath().c_str(), (*fileIt).getName().c_str());
			registerPackage((*fileIt));
		}
	}

//	debugC(kWintermuteDebugFileAccess | kWintermuteDebugLog, "  Registered %d files in %d package(s)", _files.size(), _packages.size());

	return STATUS_OK;
}
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
    }
}
Exemple #19
0
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 #20
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 #21
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;
}
Exemple #22
0
/**
 * Fallback detection scans the list of Discworld 2 targets to see if it can detect an installation
 * where the files haven't been renamed (i.e. don't have the '1' just before the extension)
 */
const ADGameDescription *TinselMetaEngine::fallbackDetect(const Common::FSList &fslist) const {
	Common::String extra;
	FileMap allFiles;
	SizeMD5Map filesSizeMD5;

	const ADGameFileDescription *fileDesc;
	const Tinsel::TinselGameDescription *g;

	if (fslist.empty())
		return NULL;

	// TODO: The following code is essentially a slightly modified copy of the
	// complete code of function detectGame() in engines/advancedDetector.cpp.
	// That quite some hefty and undesirable code duplication. Its only purpose
	// seems to be to treat filenames of the form "foo1.ext" as "foo.ext".
	// It would be nice to avoid this code duplication.

	// First we compose a hashmap of all files in fslist.
	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
		if (file->isDirectory()) {
			if (!scumm_stricmp(file->getName().c_str(), "dw2")) {
				// Probably Discworld 2 subfolder on CD, so add it's contents as well
				Common::FSList files;
				if (file->getChildren(files, Common::FSNode::kListAll)) {
					Common::FSList::const_iterator file2;
					for (file2 = files.begin(); file2 != files.end(); ++file2) {
						if (file2->isDirectory())
							continue;

						Common::String fname = file2->getName();
						allFiles[fname] = *file2;
					}
				}
			}
			continue;
		}

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

		allFiles[tstr] = *file;	// Record the presence of this file
	}

	// Check which files are included in some dw2 ADGameDescription *and* present
	// in fslist without a '1' suffix character. Compute MD5s and file sizes for these files.
	for (g = &Tinsel::gameDescriptions[0]; g->desc.gameid != 0; ++g) {
		if (strcmp(g->desc.gameid, "dw2") != 0)
			continue;

		for (fileDesc = g->desc.filesDescriptions; fileDesc->fileName; fileDesc++) {
			// Get the next filename, stripping off any '1' suffix character
			char tempFilename[50];
			strcpy(tempFilename, fileDesc->fileName);
			char *pOne = strchr(tempFilename, '1');
			if (pOne) {
				do {
					*pOne = *(pOne + 1);
					pOne++;
				} while (*pOne);
			}

			Common::String fname(tempFilename);
			if (allFiles.contains(fname) && !filesSizeMD5.contains(fname)) {
				SizeMD5 tmp;
				Common::File testFile;

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

				filesSizeMD5[fname] = tmp;
			}
		}
	}

	ADGameDescList matched;
	int maxFilesMatched = 0;

	// MD5 based matching
	for (g = &Tinsel::gameDescriptions[0]; g->desc.gameid != 0; ++g) {
		if (strcmp(g->desc.gameid, "dw2") != 0)
			continue;

		bool fileMissing = false;

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

		bool allFilesPresent = true;

		// Try to match all files for this game
		for (fileDesc = g->desc.filesDescriptions; fileDesc->fileName; fileDesc++) {
			// Get the next filename, stripping off any '1' suffix character
			char tempFilename[50];
			strcpy(tempFilename, fileDesc->fileName);
			char *pOne = strchr(tempFilename, '1');
			if (pOne) {
				do {
					*pOne = *(pOne + 1);
					pOne++;
				} while (*pOne);
			}

			Common::String tstr(tempFilename);

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

			if (fileDesc->md5 != NULL && fileDesc->md5 != filesSizeMD5[tstr].md5) {
				fileMissing = true;
				break;
			}

			if (fileDesc->fileSize != -1 && fileDesc->fileSize != filesSizeMD5[tstr].size) {
				fileMissing = true;
				break;
			}
		}

		if (!fileMissing) {
			// Count the number of matching files. Then, only keep those
			// entries which match a maximal amount of files.
			int curFilesMatched = 0;
			for (fileDesc = g->desc.filesDescriptions; fileDesc->fileName; fileDesc++)
				curFilesMatched++;

			if (curFilesMatched > maxFilesMatched) {
				maxFilesMatched = curFilesMatched;

				matched.clear();	// Remove any prior, lower ranked matches.
				matched.push_back((const ADGameDescription *)g);
			} else if (curFilesMatched == maxFilesMatched) {
				matched.push_back((const ADGameDescription *)g);
			}
		}
	}

	// We didn't find a match
	if (matched.empty())
		return NULL;

	return *matched.begin();
}
Exemple #23
0
ADDetectedGame DirectorMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const {
	// TODO: Handle Mac fallback

	// reset fallback description
	Director::DirectorGameDescription *desc = &s_fallbackDesc;
	desc->desc.gameId = "director";
	desc->desc.extra = "";
	desc->desc.language = Common::UNK_LANG;
	desc->desc.flags = ADGF_NO_FLAGS;
	desc->desc.platform = Common::kPlatformWindows;
	desc->desc.guiOptions = GUIO0();
	desc->desc.filesDescriptions[0].fileName = 0;
	desc->version = 0;
	desc->gameID = Director::GID_GENERIC;

	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
		if (file->isDirectory())
			continue;

		Common::String fileName = file->getName();
		fileName.toLowercase();
		if (!fileName.hasSuffix(".exe"))
			continue;

		Common::File f;
		if (!f.open(*file))
			continue;

		f.seek(-4, SEEK_END);

		uint32 offset = f.readUint32LE();

		if (f.eos() || offset == 0 || offset >= (uint32)(f.size() - 4))
			continue;

		f.seek(offset);

		uint32 tag = f.readUint32LE();

		switch (tag) {
		case MKTAG('3', '9', 'J', 'P'):
			desc->version = 4;
			break;
		case MKTAG('P', 'J', '9', '5'):
			desc->version = 5;
			break;
		case MKTAG('P', 'J', '0', '0'):
			desc->version = 7;
			break;
		default:
			// Prior to version 4, there was no tag here. So we'll use a bit of a
			// heuristic to detect. The first field is the entry count, of which
			// there should only be one.
			if ((tag & 0xFFFF) != 1)
				continue;

			f.skip(3);

			uint32 mmmSize = f.readUint32LE();

			if (f.eos() || mmmSize == 0)
				continue;

			byte fileNameSize = f.readByte();

			if (f.eos())
				continue;

			f.skip(fileNameSize);
			byte directoryNameSize = f.readByte();

			if (f.eos())
				continue;

			f.skip(directoryNameSize);

			if (f.pos() != f.size() - 4)
				continue;

			// Assume v3 at this point (for now at least)
			desc->version = 3;
		}

		strncpy(s_fallbackFileNameBuffer, fileName.c_str(), 50);
		s_fallbackFileNameBuffer[50] = '\0';
		desc->desc.filesDescriptions[0].fileName = s_fallbackFileNameBuffer;

		warning("Director fallback detection D%d", desc->version);

		return ADDetectedGame(&desc->desc);
	}

	return ADDetectedGame();
}
bool BaseFileManager::registerPackages() {
	debugC(kWintermuteDebugFileAccess | kWintermuteDebugLog, "Scanning packages");

	// We need the target name as a Common::String to perform some game-specific hacks.
	Common::String targetName = BaseEngine::instance().getGameTargetName();

	// Register without using SearchMan, as otherwise the FSNode-based lookup in openPackage will fail
	// and that has to be like that to support the detection-scheme.
	Common::FSList files;
	for (Common::FSList::const_iterator it = _packagePaths.begin(); it != _packagePaths.end(); ++it) {
		debugC(kWintermuteDebugFileAccess, "Should register folder: %s %s", it->getPath().c_str(), it->getName().c_str());
		if (!it->getChildren(files, Common::FSNode::kListFilesOnly)) {
			warning("getChildren() failed for path: %s", it->getDisplayName().c_str());
		}
		for (Common::FSList::const_iterator fileIt = files.begin(); fileIt != files.end(); ++fileIt) {
			// To prevent any case sensitivity issues we make the filename
			// all lowercase here. This makes the code slightly prettier
			// than the equivalent of using equalsIgnoreCase.
			Common::String fileName = fileIt->getName();
			fileName.toLowercase();

			if (!fileName.hasSuffix(".dcp")) {
				continue;
			}
			// HACK: for Reversion1, avoid loading xlanguage_pt.dcp from the main folder:
			if (_language != Common::PT_BRA && targetName.hasPrefix("reversion1")) {
				if (fileName == "xlanguage_pt.dcp") {
					continue;
				}
			}

			// Again, make the parent's name all lowercase to avoid any case
			// issues.
			Common::String parentName = fileIt->getParent().getName();
			parentName.toLowercase();

			// Avoid registering all the language files
			// TODO: Select based on the gameDesc.
			if (_language != Common::UNK_LANG && (parentName == "language" || parentName == "languages")) {
				// English
				if (_language == Common::EN_ANY && (fileName != "english.dcp" && fileName != "xlanguage_en.dcp")) {
					continue;
				// Chinese
				} else if (_language == Common::ZH_CNA && (fileName != "chinese.dcp" && fileName != "xlanguage_nz.dcp")) {
					continue;
				// Czech
				} else if (_language == Common::CZ_CZE && (fileName != "czech.dcp" && fileName != "xlanguage_cz.dcp")) {
					continue;
				// French
				} else if (_language == Common::FR_FRA && (fileName != "french.dcp" && fileName != "xlanguage_fr.dcp")) {
					continue;
				// German
				} else if (_language == Common::DE_DEU && (fileName != "german.dcp" && fileName != "xlanguage_de.dcp")) {
					continue;
				// Italian
				} else if (_language == Common::IT_ITA && (fileName != "italian.dcp" && fileName != "xlanguage_it.dcp")) {
					continue;
				// Latvian
				} else if (_language == Common::LV_LAT && (fileName != "latvian.dcp" && fileName != "xlanguage_lv.dcp")) {
					// TODO: 'latvian.dcp' is just guesswork. Is there any
					// game using Latvian and using this filename?
					continue;
				// Polish
				} else if (_language == Common::PL_POL && (fileName != "polish.dcp" && fileName != "xlanguage_pl.dcp")) {
					continue;
				// Portuguese
				} else if (_language == Common::PT_BRA && (fileName != "portuguese.dcp" && fileName != "xlanguage_pt.dcp")) {
					continue;
				// Russian
				} else if (_language == Common::RU_RUS && (fileName != "russian.dcp" && fileName != "xlanguage_ru.dcp")) {
					continue;
				}
			}
			debugC(kWintermuteDebugFileAccess, "Registering %s %s", fileIt->getPath().c_str(), fileIt->getName().c_str());
			registerPackage((*fileIt));
		}
	}

//	debugC(kWintermuteDebugFileAccess | kWintermuteDebugLog, "  Registered %d files in %d package(s)", _files.size(), _packages.size());

	return STATUS_OK;
}
Exemple #25
0
bool FrotzMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) {
	const char *const EXTENSIONS[] = { ".z1", ".z2", ".z3", ".z4", ".z5", ".z6", ".z7", ".z8",
		".dat", ".zip", nullptr };

	// Loop through the files of the folder
	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
		// Check for a recognised filename
		if (file->isDirectory())
			continue;
		Common::String filename = file->getName();
		bool hasExt = Blorb::hasBlorbExt(filename), isBlorb = false;
		for (const char *const *ext = &EXTENSIONS[0]; *ext && !hasExt; ++ext)
			hasExt = filename.hasSuffixIgnoreCase(*ext);
		if (!hasExt)
			continue;

		// Open up the file and calculate the md5, and get the serial
		Common::File gameFile;
		if (!gameFile.open(*file))
			continue;
		Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
		size_t filesize = gameFile.size();
		char serial[9] = "";
		bool emptyBlorb = false;
		gameFile.seek(0);
		isBlorb = Blorb::isBlorb(gameFile, ID_ZCOD);

		if (!isBlorb) {
			if (Blorb::hasBlorbExt(filename)) {
				gameFile.close();
				continue;
			}
			gameFile.seek(18);
			strcpy(&serial[0], "\"");
			gameFile.read(&serial[1], 6);
			strcpy(&serial[7], "\"");
		} else {
			Blorb b(*file, INTERPRETER_FROTZ);
			Common::SeekableReadStream *f = b.createReadStreamForMember("game");
			emptyBlorb = f == nullptr;

			if (!emptyBlorb) {
				f->seek(18);
				strcpy(&serial[0], "\"");
				f->read(&serial[1], 6);
				strcpy(&serial[7], "\"");
				delete f;
			}
		}
		gameFile.close();

		// Check for known games. Note that there has been some variation in exact filesizes
		// for Infocom games due to padding at the end of files. So we match on md5s for the
		// first 5Kb, and only worry about filesize for more recent Blorb based Zcode games
		const FrotzGameDescription *p = FROTZ_GAMES;
		while (p->_gameId && p->_md5 && (md5 != p->_md5 ||
				(filesize != p->_filesize && isBlorb)))
			++p;

		DetectedGame gd;
		if (!p->_gameId) {
			// Generic .dat/.zip files don't get reported as matches unless they have a known md5
			if (filename.hasSuffixIgnoreCase(".dat") || filename.hasSuffixIgnoreCase(".zip") || emptyBlorb)
				continue;

			if (gDebugLevel > 0) {
				// Print an entry suitable for putting into the detection_tables.h, using the
				// name of the parent folder the game is in as the presumed game Id
				Common::String folderName = file->getParent().getName();
				if (folderName.hasSuffix("\\"))
					folderName.deleteLastChar();
				Common::String fname = filename;
				const char *dot = strchr(fname.c_str(), '.');
				if (dot)
					fname = Common::String(fname.c_str(), dot);

				debug("ENTRY0(\"%s\", %s, \"%s\", %u),",
					fname.c_str(), strlen(serial) ? serial : "nullptr", md5.c_str(), (uint)filesize);
			}
			const PlainGameDescriptor &desc = ZCODE_GAME_LIST[0];
			gd = DetectedGame(desc.gameId, desc.description, Common::UNK_LANG, Common::kPlatformUnknown);
		} else {
			GameDescriptor gameDesc = findGame(p->_gameId);
			gd = DetectedGame(p->_gameId, gameDesc._description, p->_language, Common::kPlatformUnknown, p->_extra);
			gd.setGUIOptions(p->_guiOptions);
		}

		gd.addExtraEntry("filename", filename);
		gameList.push_back(gd);
	}

	return !gameList.empty();
}
Exemple #26
0
void MassAddDialog::handleTickle() {
	if (_scanStack.empty())
		return;	// We have finished scanning

	uint32 t = g_system->getMillis();

	// Perform a breadth-first scan of the filesystem.
	while (!_scanStack.empty() && (g_system->getMillis() - t) < kMaxScanTime) {
		Common::FSNode dir = _scanStack.pop();

		Common::FSList files;
		if (!dir.getChildren(files, Common::FSNode::kListAll)) {
			continue;
		}

		// Run the detector on the dir
		GameList candidates(EngineMan.detectGames(files));

		// Just add all detected games / game variants. If we get more than one,
		// that either means the directory contains multiple games, or the detector
		// could not fully determine which game variant it was seeing. In either
		// case, let the user choose which entries he wants to keep.
		//
		// However, we only add games which are not already in the config file.
		for (GameList::const_iterator cand = candidates.begin(); cand != candidates.end(); ++cand) {
			GameDescriptor result = *cand;
			Common::String path = dir.getPath();

			// Remove trailing slashes
			while (path != "/" && path.lastChar() == '/')
				path.deleteLastChar();

			// Check for existing config entries for this path/gameid/lang/platform combination
			if (_pathToTargets.contains(path)) {
				bool duplicate = false;
				const StringArray &targets = _pathToTargets[path];
				for (StringArray::const_iterator iter = targets.begin(); iter != targets.end(); ++iter) {
					// If the gameid, platform and language match -> skip it
					Common::ConfigManager::Domain *dom = ConfMan.getDomain(*iter);
					assert(dom);

					if ((*dom)["gameid"] == result["gameid"] &&
					    (*dom)["platform"] == result["platform"] &&
					    (*dom)["language"] == result["language"]) {
						duplicate = true;
						break;
					}
				}
				if (duplicate) {
					_oldGamesCount++;
					break;	// Skip duplicates
				}
			}
			result["path"] = path;
			_games.push_back(result);

			_list->append(result.description());
		}


		// Recurse into all subdirs
		for (Common::FSList::const_iterator file = files.begin(); file != files.end(); ++file) {
			if (file->isDirectory()) {
				_scanStack.push(*file);
			}
		}

		_dirsScanned++;
	}


	// Update the dialog
	Common::String buf;

	if (_scanStack.empty()) {
		// Enable the OK button
		_okButton->setEnabled(true);

		buf = _("Scan complete!");
		_dirProgressText->setLabel(buf);

		buf = Common::String::format(_("Discovered %d new games, ignored %d previously added games."), _games.size(), _oldGamesCount);
		_gameProgressText->setLabel(buf);

	} else {
		buf = Common::String::format(_("Scanned %d directories ..."), _dirsScanned);
		_dirProgressText->setLabel(buf);

		buf = Common::String::format(_("Discovered %d new games, ignored %d previously added games ..."), _games.size(), _oldGamesCount);
		_gameProgressText->setLabel(buf);
	}

	if (_games.size() > 0) {
		_list->scrollToEnd();
	}

	drawDialog();
}
Exemple #27
0
bool DownloadDialog::selectDirectories() {
	if (Networking::Connection::isLimited()) {
		MessageDialog alert(_("It looks like your connection is limited. "
			"Do you really want to download files with it?"), _("Yes"), _("No"));
		if (alert.runModal() != GUI::kMessageOK)
			return false;
	}

	//first user should select remote directory to download
	if (_remoteBrowser->runModal() <= 0)
		return false;

	Cloud::StorageFile remoteDirectory = _remoteBrowser->getResult();

	//now user should select local directory to download into
	if (_browser->runModal() <= 0)
		return false;

	Common::FSNode dir(_browser->getResult());
	Common::FSList files;
	if (!dir.getChildren(files, Common::FSNode::kListAll)) {
		MessageDialog alert(_("ScummVM couldn't open the specified directory!"));
		alert.runModal();
		return false;
	}

	//check that there is no file with the remote directory's name in the local one
	for (Common::FSList::iterator i = files.begin(); i != files.end(); ++i) {
		if (i->getName().equalsIgnoreCase(remoteDirectory.name())) {
			//if there is, ask user whether it's OK
			if (!i->isDirectory()) {
				GUI::MessageDialog alert(_("Cannot create a directory to download - the specified directory has a file with the same name."), _("OK"));
				alert.runModal();
				return false;
			}
			GUI::MessageDialog alert(
				Common::String::format(_("The \"%s\" already exists in the specified directory.\nDo you really want to download files into that directory?"), remoteDirectory.name().c_str()),
				_("Yes"),
				_("No")
				);
			if (alert.runModal() != GUI::kMessageOK)
				return false;
			break;
		}
	}

	//make a local path
	Common::String localPath = dir.getPath();

	//simple heuristic to determine which path separator to use
	if (localPath.size() && localPath.lastChar() != '/' && localPath.lastChar() != '\\') {
		int backslashes = 0;
		for (uint32 i = 0; i < localPath.size(); ++i)
			if (localPath[i] == '/')
				--backslashes;
			else if (localPath[i] == '\\')
				++backslashes;

		if (backslashes > 0)
			localPath += '\\' + remoteDirectory.name();
		else
			localPath += '/' + remoteDirectory.name();
	} else {
		localPath += remoteDirectory.name();
	}

	CloudMan.startDownload(remoteDirectory.path(), localPath);
	CloudMan.setDownloadTarget(this);
	_localDirectory = localPath;
	return true;
}
Exemple #28
0
GameList detectGamesImpl(const Common::FSList &fslist, bool recursion = false) {
	GameList detectedGames;
	const Sword2::GameSettings *g;
	Common::FSList::const_iterator file;
	bool isFullVersion = isFullGame(fslist);

	for (g = Sword2::sword2_settings; g->gameid; ++g) {
		// Iterate over all files in the given directory
		for (file = fslist.begin(); file != fslist.end(); ++file) {
			if (!file->isDirectory()) {
				// The required game data files can be located in the game directory, or in
				// a subdirectory called "clusters". In the latter case, we don't want to
				// detect the game in that subdirectory, as this will detect the game twice
				// when mass add is searching inside a directory. In this case, the first
				// result (the game directory) will be correct, but the second result (the
				// clusters subdirectory) will be wrong, as the optional speech, music and
				// video data files will be ignored. Note that this fix will skip the game
				// data files if the user has placed them inside a "clusters" subdirectory,
				// or if he/she points ScummVM directly to the "clusters" directory of the
				// game CD. Fixes bug #3049336.
				Common::String directory = file->getParent().getName();
				directory.toLowercase();
				if (directory.hasPrefix("clusters") && directory.size() <= 9 && !recursion)
					continue;

				if (file->getName().equalsIgnoreCase(g->detectname)) {
					// Make sure that the sword2 demo is not mixed up with the
					// full version, since they use the same filename for detection
					if ((g->features == Sword2::GF_DEMO && isFullVersion) ||
						(g->features == 0 && !isFullVersion))
						continue;

					// Match found, add to list of candidates, then abort inner loop.
					detectedGames.push_back(GameDescriptor(g->gameid, g->description, Common::UNK_LANG, Common::kPlatformUnknown, GUIO2(GUIO_NOMIDI, GUIO_NOASPECT)));
					break;
				}
			}
		}
	}


	if (detectedGames.empty()) {
		// Nothing found -- try to recurse into the 'clusters' subdirectory,
		// present e.g. if the user copied the data straight from CD.
		for (file = fslist.begin(); file != fslist.end(); ++file) {
			if (file->isDirectory()) {
				if (file->getName().equalsIgnoreCase("clusters")) {
					Common::FSList recList;
					if (file->getChildren(recList, Common::FSNode::kListAll)) {
						GameList recGames(detectGamesImpl(recList, true));
						if (!recGames.empty()) {
							detectedGames.push_back(recGames);
							break;
						}
					}
				}
			}
		}
	}


	return detectedGames;
}