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 + -
显示快捷键?