/**
 * Get CRC of the data in the specified archive.
 * Returns 0 if file could not be opened.
 */
unsigned int CArchiveScanner::GetCRC(const std::string& arcName)
{
	CRC crc;
	CArchiveBase* ar;
	std::list<std::string> files;

	//! Try to open an archive
	ar = CArchiveFactory::OpenArchive(arcName);
	if (!ar) {
		return 0; // It wasn't an archive
	}

	//! Load ignore list.
	IFileFilter* ignore = CreateIgnoreFilter(ar);

	for (unsigned fid = 0; fid != ar->NumFiles(); ++fid) {
		std::string name;
		int size;
		ar->FileInfo(fid, name, size);

		if (ignore->Match(name)) {
			continue;
		}

		StringToLowerInPlace(name); //! case insensitive hash
		files.push_back(name);
	}

	files.sort();

	//! Add all files in sorted order
	for (std::list<std::string>::iterator i = files.begin(); i != files.end(); ++i) {
		const unsigned int nameCRC = CRC().Update(i->data(), i->size()).GetDigest();
		const unsigned fid = ar->FindFile(*i);
		const unsigned int dataCRC = ar->GetCrc32(fid);
		crc.Update(nameCRC);
		crc.Update(dataCRC);
	}

	delete ignore;
	delete ar;

	unsigned int digest = crc.GetDigest();

	//! A value of 0 is used to indicate no crc.. so never return that
	//! Shouldn't happen all that often
	if (digest == 0) {
		return 4711;
	} else {
		return digest;
	}
}
void CArchiveScanner::ScanArchive(const std::string& fullName, bool doChecksum)
{
	struct stat info;
	stat(fullName.c_str(), &info);

	const std::string fn    = filesystem.GetFilename(fullName);
	const std::string fpath = filesystem.GetDirectory(fullName);
	const std::string lcfn  = StringToLower(fn);

	//! Determine whether this archive has earlier be found to be broken
	std::map<std::string, BrokenArchive>::iterator bai = brokenArchives.find(lcfn);
	if (bai != brokenArchives.end()) {
		if ((unsigned)info.st_mtime == bai->second.modified && fpath == bai->second.path) {
			bai->second.updated = true;
			return;
		}
	}

	//! Determine whether to rely on the cached info or not
	bool cached = false;

	std::map<std::string, ArchiveInfo>::iterator aii = archiveInfo.find(lcfn);
	if (aii != archiveInfo.end()) {

		//! This archive may have been obsoleted, do not process it if so
		if (aii->second.replaced.length() > 0) {
			return;
		}

		if ((unsigned)info.st_mtime == aii->second.modified && fpath == aii->second.path) {
			cached = true;
			aii->second.updated = true;
		}

		//! If we are here, we could have invalid info in the cache
		//! Force a reread if it's a directory archive, as st_mtime only
		//! reflects changes to the directory itself, not the contents.
		if (!cached) {
			archiveInfo.erase(aii);
		}
	}

	//! Time to parse the info we are interested in
	if (cached) {
		//! If cached is true, aii will point to the archive
		if (doChecksum && (aii->second.checksum == 0))
			aii->second.checksum = GetCRC(fullName);
	} else {
		CArchiveBase* ar = CArchiveFactory::OpenArchive(fullName);
		if (!ar || !ar->IsOpen()) {
			logOutput.Print("Unable to open archive: %s", fullName.c_str());
			return;
		}

		ArchiveInfo ai;
		std::string error = "";

		std::string mapfile;
		bool hasModinfo = ar->FileExists("modinfo.lua");
		bool hasMapinfo = ar->FileExists("mapinfo.lua");
		
		//! check for smf/sm3 and if the uncompression of important files is too costy
		for (unsigned fid = 0; fid != ar->NumFiles(); ++fid)
		{
			std::string name;
			int size;
			ar->FileInfo(fid, name, size);
			const std::string lowerName = StringToLower(name);
			const std::string ext = filesystem.GetExtension(lowerName);

			if ((ext == "smf") || (ext == "sm3")) {
				mapfile = name;
			}

			const unsigned char metaFileClass = GetMetaFileClass(lowerName);
			if ((metaFileClass != 0) && !(ar->HasLowReadingCost(fid))) {
				//! is a meta-file and not cheap to read
				if (metaFileClass == 1) {
					//! 1st class
					error = "Unpacking/reading cost for meta file " + name
							+ " is too high, please repack the archive (make sure to use a non-solid algorithm, if applicable)";
					break;
				} else if (metaFileClass == 2) {
					//! 2nd class
					logOutput.Print(LOG_ARCHIVESCANNER,
							"Warning: Archive %s: The cost for reading a 2nd class meta-file is too high: %s",
							fullName.c_str(), name.c_str());
				}
			}
		}

		if (!error.empty()) {
			//! we already have an error, no further evaluation required
		} if (hasMapinfo || !mapfile.empty()) {
			//! it is a map
			if (hasMapinfo) {
				ScanArchiveLua(ar, "mapinfo.lua", ai, error);
			} else if (hasModinfo) {
				//! backwards-compat for modinfo.lua in maps
				ScanArchiveLua(ar, "modinfo.lua", ai, error);
			}
			if (ai.archiveData.GetName().empty()) {
				//! FIXME The name will never be empty, if version is set (see HACK in ArchiveData)
				ai.archiveData.SetInfoItemValueString("name", filesystem.GetBasename(mapfile));
			}
			if (ai.archiveData.GetMapFile().empty()) {
				ai.archiveData.SetInfoItemValueString("mapfile", mapfile);
			}
			AddDependency(ai.archiveData.GetDependencies(), "Map Helper v1");
			ai.archiveData.SetInfoItemValueInteger("modType", modtype::map);

			logOutput.Print(LOG_ARCHIVESCANNER, "Found new map: %s", ai.archiveData.GetName().c_str());
		} else if (hasModinfo) {
			//! it is a mod
			ScanArchiveLua(ar, "modinfo.lua", ai, error);
			if (ai.archiveData.GetModType() == modtype::primary) {
				AddDependency(ai.archiveData.GetDependencies(), "Spring content v1");
			}

			logOutput.Print(LOG_ARCHIVESCANNER, "Found new game: %s", ai.archiveData.GetName().c_str());
		} else {
			//! neither a map nor a mod: error
			error = "missing modinfo.lua/mapinfo.lua";
		}

		delete ar;

		if (!error.empty()) {
			//! for some reason, the archive is marked as broken
			logOutput.Print("Failed to scan %s (%s)", fullName.c_str(), error.c_str());

			//! record it as broken, so we don't need to look inside everytime
			BrokenArchive ba;
			ba.path = fpath;
			ba.modified = info.st_mtime;
			ba.updated = true;
			ba.problem = error;
			brokenArchives[lcfn] = ba;
			return;
		}

		ai.path = fpath;
		ai.modified = info.st_mtime;
		ai.origName = fn;
		ai.updated = true;

		//! Optionally calculate a checksum for the file
		//! To prevent reading all files in all directory (.sdd) archives
		//! every time this function is called, directory archive checksums
		//! are calculated on the fly.
		if (doChecksum) {
			ai.checksum = GetCRC(fullName);
		} else {
			ai.checksum = 0;
		}

		archiveInfo[lcfn] = ai;
	}
}
Esempio n. 3
0
void CArchiveScanner::ScanArchive(const std::string& fullName, bool doChecksum)
{
	struct stat info;
	stat(fullName.c_str(), &info);

	const std::string fn    = filesystem.GetFilename(fullName);
	const std::string fpath = filesystem.GetDirectory(fullName);
	const std::string lcfn  = StringToLower(fn);

	// Determine whether this archive has earlier be found to be broken
	std::map<std::string, BrokenArchive>::iterator bai = brokenArchives.find(lcfn);
	if (bai != brokenArchives.end()) {
		if ((unsigned)info.st_mtime == bai->second.modified && fpath == bai->second.path) {
			bai->second.updated = true;
			return;
		}
	}

	// Determine whether to rely on the cached info or not
	bool cached = false;

	std::map<std::string, ArchiveInfo>::iterator aii = archiveInfo.find(lcfn);
	if (aii != archiveInfo.end()) {

		// This archive may have been obsoleted, do not process it if so
		if (aii->second.replaced.length() > 0) {
			return;
		}

		if ((unsigned)info.st_mtime == aii->second.modified && fpath == aii->second.path) {
			cached = true;
			aii->second.updated = true;
		}

		// If we are here, we could have invalid info in the cache
		// Force a reread if it's a directory archive, as st_mtime only
		// reflects changes to the directory itself, not the contents.
		if (!cached) {
			archiveInfo.erase(aii);
		}
	}

	// Time to parse the info we are interested in
	if (cached) {
		// If cached is true, aii will point to the archive
		if (doChecksum && (aii->second.checksum == 0))
			aii->second.checksum = GetCRC(fullName);
	} else {
		CArchiveBase* ar = CArchiveFactory::OpenArchive(fullName);
		if (ar && ar->IsOpen()) {
			ArchiveInfo ai;

			std::string mapfile;
			bool hasModinfo = false;
			bool hasMapinfo = false;
			for (unsigned fid = 0; fid != ar->NumFiles(); ++fid)
			{
				std::string name;
				int size;
				ar->FileInfo(fid, name, size);

				const std::string lowerName = StringToLower(name);
				const std::string ext = filesystem.GetExtension(lowerName);

				if ((ext == "smf") || (ext == "sm3")) {
					mapfile = name;
				} else if (lowerName == "modinfo.lua") {
					hasModinfo = true;
				} else if (lowerName == "mapinfo.lua") {
					hasMapinfo = true;
				}
			}

			if (hasMapinfo || !mapfile.empty()) {
				// it is a map
				if (hasMapinfo) {
					ScanArchiveLua(ar, "mapinfo.lua", ai);
				} else if (hasModinfo) {
					// backwards-compat for modinfo.lua in maps
					ScanArchiveLua(ar, "modinfo.lua", ai);
				}
				if (ai.archiveData.name.empty()) {
					ai.archiveData.name = filesystem.GetBasename(mapfile);
				}
				if (ai.archiveData.mapfile.empty()) {
					ai.archiveData.mapfile = mapfile;
				}
				AddDependency(ai.archiveData.dependencies, "Map Helper v1");
				ai.archiveData.modType = modtype::map;
				
			} else if (hasModinfo) {
				// it is a mod
				ScanArchiveLua(ar, "modinfo.lua", ai);
				if (ai.archiveData.modType == modtype::primary)
					AddDependency(ai.archiveData.dependencies, "Spring content v1");
			} else {
				// neither a map nor a mod: error
				logOutput.Print(LOG_ARCHIVESCANNER, "Failed to scan %s (missing modinfo.lua/mapinfo.lua)", fullName.c_str());
				delete ar;

				// record it as broken, so we don't need to look inside everytime
				BrokenArchive ba;
				ba.path = fpath;
				ba.modified = info.st_mtime;
				ba.updated = true;
				brokenArchives[lcfn] = ba;
				return;
			}

			ai.path = fpath;
			ai.modified = info.st_mtime;
			ai.origName = fn;
			ai.updated = true;

			delete ar;

			// Optionally calculate a checksum for the file
			// To prevent reading all files in all directory (.sdd) archives
			// every time this function is called, directory archive checksums
			// are calculated on the fly.
			if (doChecksum) {
				ai.checksum = GetCRC(fullName);
			} else {
				ai.checksum = 0;
			}

			archiveInfo[lcfn] = ai;
		} else {
			logOutput.Print(LOG_ARCHIVESCANNER, "Unable to open archive: %s", fullName.c_str());
		}
	}
}