archivescanner.cpp

来自「这是整套横扫千军3D版游戏的源码」· C++ 代码 · 共 731 行 · 第 1/2 页

CPP
731
字号
#include "ArchiveScanner.h"

#include <algorithm>
#include <sys/types.h>
#include <sys/stat.h>

#include "ArchiveFactory.h"
#include "ArchiveBuffered.h"
#include "CRC.h"
#include "FileHandler.h"
#include "Platform/FileSystem.h"
#include "TdfParser.h"
#include "mmgr.h"

extern "C" {
#include "lib/7zip/7zCrc.h"
};

// fix for windows
#ifndef S_ISDIR
#define S_ISDIR(x) (((x) & 0170000) == 0040000) /* directory */
#endif

/*
 * The archive scanner is used to find stuff in archives that are needed before building the virtual
 * filesystem. This currently includes maps and mods. It uses caching to speed up the process.
 *
 * It only retrieves info that is used in an initial listing.. For detailed info when selecting a map
 * for example the more specialised parsers will be used. (mapping one archive when selecting a map
 * is not slow, but mapping them all every time to make the list is)
 */

#define INTERNAL_VER	6

CArchiveScanner* archiveScanner = NULL;

CArchiveScanner::CArchiveScanner(void) :
	isDirty(false)
{
}

CArchiveScanner::~CArchiveScanner(void)
{
	if (isDirty)
		WriteCacheData(filesystem.LocateFile(GetFilename(), FileSystem::WRITE));
}

std::string CArchiveScanner::GetFilename()
{
	char buf[32];
	sprintf(buf, "ArchiveCacheV%i.txt", INTERNAL_VER);
	return string(buf);
}

CArchiveScanner::ModData CArchiveScanner::GetModData(TdfParser* p, const string& section)
{
	ModData md;
	md.name = "";

	if (!p->SectionExist(section)) {
		return md;
	}

	md.name        = p->SGetValueDef("", section + "\\Name");
	md.shortName   = p->SGetValueDef("", section + "\\ShortName");
	md.version     = p->SGetValueDef("", section + "\\Version");
	md.mutator     = p->SGetValueDef("", section + "\\Mutator");
	md.game        = p->SGetValueDef("", section + "\\Game");
	md.shortGame   = p->SGetValueDef("", section + "\\ShortGame");
	md.description = p->SGetValueDef("", section + "\\Description");

	md.modType = atoi(p->SGetValueDef("0", section + "\\ModType").c_str());

	int numDep = atoi(p->SGetValueDef("0", section + "\\NumDependencies").c_str());
	for (int dep = 0; dep < numDep; ++dep) {
		char key[100];
		sprintf(key, "%s\\Depend%d", section.c_str(), dep);
		md.dependencies.push_back(p->SGetValueDef("", key));
	}

	int numReplace = atoi(p->SGetValueDef("0", (section + "\\NumReplaces").c_str()).c_str());
	for (int rep = 0; rep < numReplace; ++rep) {
		char key[100];
		sprintf(key, "%s\\Replace%d", section.c_str(), rep);
		md.replaces.push_back(p->SGetValueDef("", key));
	}

	// HACK needed until lobbies, lobbyserver and unitsync are sorted out
	// so they can uniquely identify different versions of the same mod.
	// (at time of this writing they use name only)

	// NOTE when changing this, this function is used both by the code that
	// reads ArchiveCache.txt and the code that reads modinfo.tdf from the mod.
	// so make sure it doesn't keep adding stuff to the name everytime
	// Spring/unitsync is loaded.

	if (md.name.find(md.version) == string::npos) {
		md.name += " " + md.version;
	}

	return md;
}

void CArchiveScanner::Scan(const string& curPath, bool checksum)
{
	InitCrcTable();
	isDirty = true;

	const int flags = (FileSystem::INCLUDE_DIRS | FileSystem::RECURSE);
	std::vector<std::string> found = filesystem.FindFiles(curPath, "*", flags);

	for (std::vector<std::string>::iterator it = found.begin(); it != found.end(); ++it) {
		string fullName = *it;

		// Strip
		const char lastFullChar = fullName[fullName.size() - 1];
		if ((lastFullChar == '/') || (lastFullChar == '\\')) {
			fullName = fullName.substr(0, fullName.size() - 1);
		}

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

		// Exclude archivefiles found inside directory archives (.sdd)
		if (lcfpath.find(".sdd") != string::npos) {
			continue;
		}

		// Exclude archivefiles found inside hidden directories
		if ((lcfpath.find("/hidden/")   != string::npos) ||
		    (lcfpath.find("\\hidden\\") != string::npos)) {
			continue;
		}

		// Is this an archive we should look into?
		if (CArchiveFactory::IsArchive(fullName)) {
			struct stat info;

			stat(fullName.c_str(), &info);

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

			map<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)
					continue;

				/*
					For truely correct updating of .sdd archives, this code should
					be enabled. Unfortunately it has as side effect that all files
					in all .sdd's always need to be stat()'ed, which really slows
					down program startup.

					An update can be forced anyway by removing ArchiveCacheV*.txt
					or renaming the archive.
				*/

				/*if (S_ISDIR(info.st_mode)) {
					struct stat info2;
					std::vector<std::string> sddfiles = filesystem.FindFiles(fpath, "*", FileSystem::RECURSE | FileSystem::INCLUDE_DIRS);
					for (std::vector<std::string>::iterator sddit = found.begin(); sddit != found.end(); ++sddit) {
						stat(sddit->c_str(), &info2);
						if (info.st_mtime < info2.st_mtime) {
							info.st_mtime = info2.st_mtime;
						}
					}
				}*/

				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) {

				//printf("scanning archive: %s\n", fullName.c_str());

				CArchiveBase* ar = CArchiveFactory::OpenArchive(fullName);
				if (ar) {
					int cur;
					string name;
					int size;
					ArchiveInfo ai;

					cur = ar->FindFiles(0, &name, &size);
					while (cur != 0) {
						//printf("found %s %d\n", name.c_str(), size);

						string ext = StringToLower(name.substr(name.find_last_of('.') + 1));

						// only accept new format maps
						if (ext == "smf" || ext == "sm3") {
							MapData md;
							if (name.find_last_of('\\') == string::npos && name.find_last_of('/') == string::npos) {
								md.name = name;
								md.virtualPath = "/";
							}
							else {
								if (name.find_last_of('\\') == string::npos) {
									md.name = name.substr(name.find_last_of('/') + 1);
									md.virtualPath = name.substr(0, name.find_last_of('/') + 1);	// include the backslash
								} else {
									md.name = name.substr(name.find_last_of('\\') + 1);
									md.virtualPath = name.substr(0, name.find_last_of('\\') + 1);	// include the backslash
								}
								//md.name = md.name.substr(0, md.name.find_last_of('.'));
							}
							ai.mapData.push_back(md);
						}

						if (name == "modinfo.tdf") {
							int fh = ar->OpenFile(name);
							if (fh) {
								int fsize = ar->FileSize(fh);

								char* buf = SAFE_NEW char[fsize];
								ar->ReadFile(fh, buf, fsize);
								ar->CloseFile(fh);
								try {
									TdfParser p( buf, fsize );
									ai.modData = GetModData(&p, "mod");
								} catch (const TdfParser::parse_error&) {
									// Silently ignore mods with parse errors
								}
								delete [] buf;
							}

						}

						cur = ar->FindFiles(cur, &name, &size);
					}

					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 (checksum) {
						ai.checksum = GetCRC(fullName);
					}
					else {
						ai.checksum = 0;
					}

					archiveInfo[lcfn] = ai;
				}
			}
			else {
				// If cached is true, aii will point to the archive
				if ((checksum) && (aii->second.checksum == 0)) {
					aii->second.checksum = GetCRC(fullName);
				}
			}
		}
	}

	// Now we'll have to parse the replaces-stuff found in the mods
	for (map<string, ArchiveInfo>::iterator aii = archiveInfo.begin(); aii != archiveInfo.end(); ++aii) {
		for (vector<string>::iterator i = aii->second.modData.replaces.begin(); i != aii->second.modData.replaces.end(); ++i) {

			string lcname = StringToLower(*i);

			map<string, ArchiveInfo>::iterator ar = archiveInfo.find(lcname);

			// If it's not there, we will create a new entry
			if (ar == archiveInfo.end()) {
				ArchiveInfo tmp;
				archiveInfo[lcname] = tmp;
				ar = archiveInfo.find(lcname);
			}

			// Overwrite the info for this archive with a replaced pointer
			ar->second.path = "";
			ar->second.origName = lcname;
			ar->second.modified = 1;
			ar->second.mapData.clear();
			ar->second.modData.name = "";
			ar->second.modData.replaces.clear();
			ar->second.updated = true;
			ar->second.replaced = aii->first;
		}
	}
}

/** Get CRC of the data in the specified file. Returns 0 if file could not be opened. */

unsigned int CArchiveScanner::GetCRC(const string& filename)
{
	UInt32 crc;
	UInt32 digest = 0;
	CArchiveBase* ar;
	list<string> files;
	string innerName;
	string lowerName;
	int innerSize;
	int cur = 0;

	CrcInit(&crc);

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

	// Sort all file paths for deterministic behaviour
	while (true) {
		cur = ar->FindFiles(cur, &innerName, &innerSize);
		if (cur == 0) break;
		lowerName = StringToLower(innerName); // case insensitive hash
		files.push_back(lowerName);
	}
	files.sort();

	// Add all files in sorted order
	for (list<string>::iterator i = files.begin(); i != files.end(); i++ ) {
		digest = CrcCalculateDigest(i->data(), i->size());
		CrcUpdateUInt32(&crc, digest);
		CrcUpdateUInt32(&crc, ar->GetCrc32(*i));
	}
	delete ar;

	digest = CrcGetDigest(&crc);

	// 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::ReadCacheData(const std::string& filename)
{
	TdfParser p;

	try {
		p.LoadFile(filename);
	} catch (const content_error&) {
		return;
	}

	// Do not load old version caches
	int ver = atoi(p.SGetValueDef("0", "archivecache\\internalver").c_str());
	if (ver != INTERNAL_VER)
		return;

	int numArs = atoi(p.SGetValueDef("0", "archivecache\\numarchives").c_str());

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?