1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 
4 #include <list>
5 #include <algorithm>
6 #include <stdio.h>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <boost/scoped_ptr.hpp>
10 
11 #include "ArchiveScanner.h"
12 #include "ArchiveLoader.h"
13 #include "DataDirLocater.h"
14 #include "Archives/IArchive.h"
15 #include "FileFilter.h"
16 #include "DataDirsAccess.h"
17 #include "FileSystem.h"
18 #include "FileQueryFlags.h"
19 #include "Lua/LuaParser.h"
20 #include "System/Log/ILog.h"
21 #include "System/CRC.h"
22 #include "System/Util.h"
23 #include "System/Exceptions.h"
24 #include "System/ThreadPool.h"
25 #if       !defined(DEDICATED) && !defined(UNITSYNC)
26 #include "System/Platform/Watchdog.h"
27 #endif // !defined(DEDICATED) && !defined(UNITSYNC)
28 
29 
30 #define LOG_SECTION_ARCHIVESCANNER "ArchiveScanner"
31 LOG_REGISTER_SECTION_GLOBAL(LOG_SECTION_ARCHIVESCANNER)
32 
33 
34 /*
35  * The archive scanner is used to find stuff in archives
36  * which are needed before building the virtual filesystem.
37  * This currently includes maps and mods.
38  * It uses caching to speed up the process.
39  *
40  * It only retrieves info that is used in an initial listing.
41  * For detailed info when selecting a map for example,
42  * the more specialized parsers will be used.
43  * (mapping one archive when selecting a map is not slow,
44  * but mapping them all, every time to make the list is)
45  */
46 
47 const int INTERNAL_VER = 10;
48 CArchiveScanner* archiveScanner = NULL;
49 
50 
51 
52 /*
53  * Engine known (and used?) tags in [map|mod]info.lua
54  */
55 struct KnownInfoTag {
56 	std::string name;
57 	std::string desc;
58 	bool must_have;
59 };
60 
61 const KnownInfoTag knownTags[] = {
62 	{"name",        "example: Original Total Annihilation", true},
63 	{"shortname",   "example: OTA", false},
64 	{"version",     "example: v2.3", false},
65 	{"mutator",     "example: deployment", false},
66 	{"game",        "example: Total Annihilation", false},
67 	{"shortgame",   "example: TA", false},
68 	{"description", "example: Little units blowing up other little units", false},
69 	{"mapfile",     "in case its a map, store location of smf/sm3 file", false}, //FIXME is this ever used in the engine?! or does it auto calc the location?
70 	{"modtype",     "1=primary, 0=hidden, 3=map", true},
71 	{"depend",      "a table with all archives that needs to be loaded for this one", false},
72 	{"replace",     "a table with archives that got replaced with this one", false}
73 };
74 
75 /*
76  * CArchiveScanner::ArchiveData
77  */
ArchiveData(const LuaTable & archiveTable,bool fromCache)78 CArchiveScanner::ArchiveData::ArchiveData(const LuaTable& archiveTable, bool fromCache)
79 {
80 	if (!archiveTable.IsValid()) {
81 		return;
82 	}
83 
84 	std::vector<std::string> keys;
85 	if (!archiveTable.GetKeys(keys)) {
86 		return;
87 	}
88 
89 	for (std::string& key: keys) {
90 		const std::string keyLower = StringToLower(key);
91 		if (!ArchiveData::IsReservedKey(keyLower)) {
92 			if (keyLower == "modtype") {
93 				SetInfoItemValueInteger(key, archiveTable.GetInt(key, 0));
94 				continue;
95 			}
96 
97 			const int luaType = archiveTable.GetType(key);
98 			switch (luaType) {
99 				case LuaTable::STRING: {
100 					SetInfoItemValueString(key, archiveTable.GetString(key, ""));
101 				} break;
102 				case LuaTable::NUMBER: {
103 					SetInfoItemValueFloat(key, archiveTable.GetFloat(key, 0.0f));
104 				} break;
105 				case LuaTable::BOOLEAN: {
106 					SetInfoItemValueBool(key, archiveTable.GetBool(key, false));
107 				} break;
108 				default: {
109 					// just ignore unsupported types (most likely to be lua-tables)
110 					//throw content_error("Lua-type " + IntToString(luaType) + " not supported in archive-info, but it is used on key \"" + *key + "\"");
111 				} break;
112 			}
113 		}
114 	}
115 
116 	const LuaTable _dependencies = archiveTable.SubTable("depend");
117 
118 	for (int dep = 1; _dependencies.KeyExists(dep); ++dep) {
119 		dependencies.push_back(_dependencies.GetString(dep, ""));
120 	}
121 
122 	const LuaTable _replaces = archiveTable.SubTable("replace");
123 	for (int rep = 1; _replaces.KeyExists(rep); ++rep) {
124 		replaces.push_back(_replaces.GetString(rep, ""));
125 	}
126 
127 	// FIXME
128 	// XXX HACK needed until lobbies, lobbyserver and unitsync are sorted out
129 	// so they can uniquely identify different versions of the same mod.
130 	// (at time of this writing they use name only)
131 
132 	// NOTE when changing this, this function is used both by the code that
133 	// reads ArchiveCache.lua and the code that reads modinfo.lua from the mod.
134 	// so make sure it doesn't keep adding stuff to the name everytime
135 	// Spring/unitsync is loaded.
136 
137 	const std::string& name = GetNameVersioned();
138 	const std::string& version = GetVersion();
139 	if (!version.empty()) {
140 		if (name.find(version) == std::string::npos) {
141 			SetInfoItemValueString("name", name + " " + version);
142 		} else if (!fromCache) {
143 			LOG_L(L_WARNING, "Invalid Name detected, please contact the author of the archive to remove the Version from the Name: %s, Version: %s", name.c_str(), version.c_str());
144 		}
145 	}
146 
147 	if (GetName().empty())
148 		SetInfoItemValueString("name_pure", name);
149 }
150 
GetKeyDescription(const std::string & keyLower)151 std::string CArchiveScanner::ArchiveData::GetKeyDescription(const std::string& keyLower)
152 {
153 	static const int extCount = sizeof(knownTags) / sizeof(KnownInfoTag);
154 	for (int i = 0; i < extCount; ++i) {
155 		const KnownInfoTag tag = knownTags[i];
156 		if (keyLower == tag.name) {
157 			return tag.desc;
158 		}
159 	}
160 
161 	return "<custom property>";
162 }
163 
164 
IsReservedKey(const std::string & keyLower)165 bool CArchiveScanner::ArchiveData::IsReservedKey(const std::string& keyLower)
166 {
167 	return ((keyLower == "depend") || (keyLower == "replace"));
168 }
169 
170 
IsValid(std::string & err) const171 bool CArchiveScanner::ArchiveData::IsValid(std::string& err) const
172 {
173 	std::string missingtag;
174 
175 	static const int extCount = sizeof(knownTags) / sizeof(KnownInfoTag);
176 	for (int i = 0; i < extCount; ++i) {
177 		const KnownInfoTag tag = knownTags[i];
178 		if (tag.must_have && (info.find(tag.name) == info.end())) {
179 			missingtag = tag.name;
180 			break;
181 		}
182 	}
183 
184 	if (missingtag.empty()) {
185 		return true;
186 	} else {
187 		err = "Missing tag \"" + missingtag+ "\".";
188 		return false;
189 	}
190 }
191 
192 
GetInfoItem(const std::string & key)193 InfoItem* CArchiveScanner::ArchiveData::GetInfoItem(const std::string& key)
194 {
195 	InfoItem* infoItem = NULL;
196 
197 	const std::map<std::string, InfoItem>::iterator ii = info.find(StringToLower(key));
198 	if (ii != info.end()) {
199 		infoItem = &(ii->second);
200 	}
201 
202 	return infoItem;
203 }
204 
GetInfoItem(const std::string & key) const205 const InfoItem* CArchiveScanner::ArchiveData::GetInfoItem(const std::string& key) const
206 {
207 	const InfoItem* infoItem = NULL;
208 
209 	const std::map<std::string, InfoItem>::const_iterator ii = info.find(StringToLower(key));
210 	if (ii != info.end()) {
211 		infoItem = &(ii->second);
212 	}
213 
214 	return infoItem;
215 }
216 
EnsureInfoItem(const std::string & key)217 InfoItem& CArchiveScanner::ArchiveData::EnsureInfoItem(const std::string& key)
218 {
219 	const std::string& keyLower = StringToLower(key);
220 
221 	if (IsReservedKey(keyLower)) {
222 		throw content_error("You may not use key " + key + " in archive info, as it is reserved.");
223 	}
224 
225 	const std::map<std::string, InfoItem>::iterator ii = info.find(keyLower);
226 	if (ii == info.end()) {
227 		// add a new info-item
228 		InfoItem& infoItem = info[keyLower];
229 		infoItem.key = key;
230 		infoItem.valueType = INFO_VALUE_TYPE_INTEGER;
231 		infoItem.value.typeInteger = 0;
232 		return infoItem;
233 	}
234 
235 	return ii->second;
236 }
237 
SetInfoItemValueString(const std::string & key,const std::string & value)238 void CArchiveScanner::ArchiveData::SetInfoItemValueString(const std::string& key, const std::string& value)
239 {
240 	InfoItem& infoItem = EnsureInfoItem(key);
241 	infoItem.valueType = INFO_VALUE_TYPE_STRING;
242 	infoItem.valueTypeString = value;
243 }
244 
SetInfoItemValueInteger(const std::string & key,int value)245 void CArchiveScanner::ArchiveData::SetInfoItemValueInteger(const std::string& key, int value)
246 {
247 	InfoItem& infoItem = EnsureInfoItem(key);
248 	infoItem.valueType = INFO_VALUE_TYPE_INTEGER;
249 	infoItem.value.typeInteger = value;
250 }
251 
SetInfoItemValueFloat(const std::string & key,float value)252 void CArchiveScanner::ArchiveData::SetInfoItemValueFloat(const std::string& key, float value)
253 {
254 	InfoItem& infoItem = EnsureInfoItem(key);
255 	infoItem.valueType = INFO_VALUE_TYPE_FLOAT;
256 	infoItem.value.typeFloat = value;
257 }
258 
SetInfoItemValueBool(const std::string & key,bool value)259 void CArchiveScanner::ArchiveData::SetInfoItemValueBool(const std::string& key, bool value)
260 {
261 	InfoItem& infoItem = EnsureInfoItem(key);
262 	infoItem.valueType = INFO_VALUE_TYPE_BOOL;
263 	infoItem.value.typeBool = value;
264 }
265 
266 
GetInfoItems() const267 std::vector<InfoItem> CArchiveScanner::ArchiveData::GetInfoItems() const
268 {
269 	std::vector<InfoItem> infoItems;
270 
271 	for (std::map<std::string, InfoItem>::const_iterator i = info.begin(); i != info.end(); ++i) {
272 		infoItems.push_back(i->second);
273 		infoItems.at(infoItems.size() - 1).desc = GetKeyDescription(i->first);
274 	}
275 
276 	return infoItems;
277 }
278 
279 
GetInfoValueString(const std::string & key) const280 std::string CArchiveScanner::ArchiveData::GetInfoValueString(const std::string& key) const
281 {
282 	std::string valueString = "";
283 
284 	const InfoItem* infoItem = GetInfoItem(key);
285 	if (infoItem != NULL) {
286 		if (infoItem->valueType == INFO_VALUE_TYPE_STRING) {
287 			valueString = infoItem->valueTypeString;
288 		} else {
289 			valueString = info_getValueAsString(infoItem);
290 		}
291 	}
292 
293 	return valueString;
294 }
295 
GetInfoValueInteger(const std::string & key) const296 int CArchiveScanner::ArchiveData::GetInfoValueInteger(const std::string& key) const
297 {
298 	int value = 0;
299 
300 	const InfoItem* infoItem = GetInfoItem(key);
301 	if ((infoItem != NULL) && (infoItem->valueType == INFO_VALUE_TYPE_INTEGER)) {
302 		value = infoItem->value.typeInteger;
303 	}
304 
305 	return value;
306 }
307 
GetInfoValueFloat(const std::string & key) const308 float CArchiveScanner::ArchiveData::GetInfoValueFloat(const std::string& key) const
309 {
310 	float value = 0.0f;
311 
312 	const InfoItem* infoItem = GetInfoItem(key);
313 	if ((infoItem != NULL) && (infoItem->valueType == INFO_VALUE_TYPE_FLOAT)) {
314 		value = infoItem->value.typeFloat;
315 	}
316 
317 	return value;
318 }
319 
GetInfoValueBool(const std::string & key) const320 bool CArchiveScanner::ArchiveData::GetInfoValueBool(const std::string& key) const
321 {
322 	bool value = false;
323 
324 	const InfoItem* infoItem = GetInfoItem(key);
325 	if ((infoItem != NULL) && (infoItem->valueType == INFO_VALUE_TYPE_BOOL)) {
326 		value = infoItem->value.typeBool;
327 	}
328 
329 	return value;
330 }
331 
332 
333 
334 
335 
336 /*
337  * CArchiveScanner
338  */
339 
CArchiveScanner()340 CArchiveScanner::CArchiveScanner()
341 : isDirty(false)
342 {
343 	// the "cache" dir is created in DataDirLocater
344 	const std:: string cacheFolder = dataDirLocater.GetWriteDirPath() + FileSystem::EnsurePathSepAtEnd(FileSystem::GetCacheBaseDir());
345 	cachefile = cacheFolder + IntToString(INTERNAL_VER, "ArchiveCache%i.lua");
346 	ReadCacheData(GetFilepath());
347 	if (archiveInfos.empty()) {
348 		// when versioned ArchiveCache%i.lua is missing or empty, try old unversioned filename
349 		ReadCacheData(cacheFolder + "ArchiveCache.lua");
350 	}
351 
352 	const std::vector<std::string>& datadirs = dataDirLocater.GetDataDirPaths();
353 	std::vector<std::string> scanDirs;
354 	for (auto d = datadirs.rbegin(); d != datadirs.rend(); ++d) {
355 		scanDirs.push_back(*d + "maps");
356 		scanDirs.push_back(*d + "base");
357 		scanDirs.push_back(*d + "games");
358 		scanDirs.push_back(*d + "packages");
359 	}
360 	// ArchiveCache has been parsed at this point --> archiveInfos is populated
361 	ScanDirs(scanDirs, true);
362 	WriteCacheData(GetFilepath());
363 }
364 
365 
~CArchiveScanner()366 CArchiveScanner::~CArchiveScanner()
367 {
368 	if (isDirty) {
369 		WriteCacheData(GetFilepath());
370 	}
371 }
372 
373 
GetFilepath() const374 const std::string& CArchiveScanner::GetFilepath() const
375 {
376 	return cachefile;
377 }
378 
379 
ScanDirs(const std::vector<std::string> & scanDirs,bool doChecksum)380 void CArchiveScanner::ScanDirs(const std::vector<std::string>& scanDirs, bool doChecksum)
381 {
382 	isDirty = true;
383 
384 	// scan for all archives
385 	std::list<std::string> foundArchives;
386 	for (const std::string& dir: scanDirs) {
387 		if (FileSystem::DirExists(dir)) {
388 			LOG("Scanning: %s", dir.c_str());
389 			ScanDir(dir, &foundArchives);
390 		}
391 	}
392 
393 	// check for duplicates reached by links
394 	//XXX too slow also ScanArchive() skips duplicates itself, too
395 	/*for (auto it = foundArchives.begin(); it != foundArchives.end(); ++it) {
396 		auto jt = it;
397 		++jt;
398 		while (jt != foundArchives.end()) {
399 			std::string f1 = StringToLower(FileSystem::GetFilename(*it));
400 			std::string f2 = StringToLower(FileSystem::GetFilename(*jt));
401 			if ((f1 == f2) || FileSystem::ComparePaths(*it, *jt)) {
402 				jt = foundArchives.erase(jt);
403 			} else {
404 				++jt;
405 			}
406 		}
407 	}*/
408 
409 	// Create archiveInfos etc. when not being in cache already
410 	for (const std::string& archive: foundArchives) {
411 		ScanArchive(archive, doChecksum);
412 	#if !defined(DEDICATED) && !defined(UNITSYNC)
413 		Watchdog::ClearTimer(WDT_MAIN);
414 	#endif
415 	}
416 
417 	// Now we'll have to parse the replaces-stuff found in the mods
418 	for (auto& aii: archiveInfos) {
419 		for (std::string& replaceName: aii.second.archiveData.GetReplaces()) {
420 			// Overwrite the info for this archive with a replaced pointer
421 			const std::string lcname = StringToLower(replaceName);
422 			ArchiveInfo& ai = archiveInfos[lcname];
423 			ai.path = "";
424 			ai.origName = lcname;
425 			ai.modified = 1;
426 			ai.archiveData = ArchiveData();
427 			ai.updated = true;
428 			ai.replaced = aii.first;
429 		}
430 	}
431 }
432 
433 
ScanDir(const std::string & curPath,std::list<std::string> * foundArchives)434 void CArchiveScanner::ScanDir(const std::string& curPath, std::list<std::string>* foundArchives)
435 {
436 	// check recursive dirs when NOT being sdd's!
437 	std::list<std::string> subDirs;
438 	subDirs.push_back(curPath);
439 
440 	while (!subDirs.empty()) {
441 		FileSystem::EnsurePathSepAtEnd(subDirs.front());
442 		const std::vector<std::string>& found = dataDirsAccess.FindFiles(subDirs.front(), "*", FileQueryFlags::INCLUDE_DIRS);
443 		subDirs.pop_front();
444 
445 		for (std::string fullName: found) {
446 			FileSystem::EnsureNoPathSepAtEnd(fullName);
447 			const std::string lcfpath = StringToLower(FileSystem::GetDirectory(fullName));
448 
449 			// Exclude archivefiles found inside directory archives (.sdd)
450 			if (lcfpath.find(".sdd") != std::string::npos) {
451 				continue;
452 			}
453 
454 			// Is this an archive we should look into?
455 			if (archiveLoader.IsArchiveFile(fullName)) {
456 				foundArchives->push_front(fullName); // push by reversed order!
457 			} else
458 			if (FileSystem::DirExists(fullName)) {
459 				subDirs.push_back(fullName);
460 			}
461 		}
462 	}
463 }
464 
AddDependency(std::vector<std::string> & deps,const std::string & dependency)465 static void AddDependency(std::vector<std::string>& deps, const std::string& dependency)
466 {
467 	auto it = std::find(deps.begin(), deps.end(), dependency);
468 	if (it != deps.end()) return;
469 	deps.push_back(dependency);
470 }
471 
CheckCompression(const IArchive * ar,const std::string & fullName,std::string & error)472 bool CArchiveScanner::CheckCompression(const IArchive* ar,const std::string& fullName, std::string& error)
473 {
474 	if (!ar->CheckForSolid())
475 		return true;
476 	for (unsigned fid = 0; fid != ar->NumFiles(); ++fid) {
477 		std::string name;
478 		int size;
479 		ar->FileInfo(fid, name, size);
480 		const std::string lowerName = StringToLower(name);
481 		const auto metaFileClass = CArchiveScanner::GetMetaFileClass(lowerName);
482 		if ((metaFileClass == 0) || ar->HasLowReadingCost(fid))
483 			continue;
484 
485 		// is a meta-file and not cheap to read
486 		if (metaFileClass == 1) {
487 			// 1st class
488 			error = "Unpacking/reading cost for meta file " + name
489 					+ " is too high, please repack the archive (make sure to use a non-solid algorithm, if applicable)";
490 			return false;
491 		} else if (metaFileClass == 2) {
492 			// 2nd class
493 			LOG_SL(LOG_SECTION_ARCHIVESCANNER, L_WARNING,
494 					"Archive %s: The cost for reading a 2nd-class meta-file is too high: %s",
495 					fullName.c_str(), name.c_str());
496 		}
497 
498 	}
499 	return true;
500 }
501 
SearchMapFile(const IArchive * ar,std::string & error)502 std::string CArchiveScanner::SearchMapFile(const IArchive* ar, std::string& error)
503 {
504 	assert(ar!=NULL);
505 
506 	// check for smf/sm3 and if the uncompression of important files is too costy
507 	for (unsigned fid = 0; fid != ar->NumFiles(); ++fid) {
508 		std::string name;
509 		int size;
510 		ar->FileInfo(fid, name, size);
511 		const std::string lowerName = StringToLower(name);
512 		const std::string ext = FileSystem::GetExtension(lowerName);
513 
514 		if ((ext == "smf") || (ext == "sm3")) {
515 			return name;
516 		}
517 
518 	}
519 	return "";
520 }
521 
ScanArchive(const std::string & fullName,bool doChecksum)522 void CArchiveScanner::ScanArchive(const std::string& fullName, bool doChecksum)
523 {
524 	const std::string fn    = FileSystem::GetFilename(fullName);
525 	const std::string fpath = FileSystem::GetDirectory(fullName);
526 	const std::string lcfn  = StringToLower(fn);
527 
528 	// Stat file
529 	struct stat info = {0};
530 	int statfailed = stat(fullName.c_str(), &info);
531 
532 	// If stat fails, assume the archive is not broken nor cached
533 	if (!statfailed) {
534 		// Determine whether this archive has earlier be found to be broken
535 		std::map<std::string, BrokenArchive>::iterator bai = brokenArchives.find(lcfn);
536 		if (bai != brokenArchives.end()) {
537 			if ((unsigned)info.st_mtime == bai->second.modified && fpath == bai->second.path) {
538 				bai->second.updated = true;
539 				return;
540 			}
541 		}
542 
543 		// Determine whether to rely on the cached info or not
544 		std::map<std::string, ArchiveInfo>::iterator aii = archiveInfos.find(lcfn);
545 		if (aii != archiveInfos.end()) {
546 			// This archive may have been obsoleted, do not process it if so
547 			if (!aii->second.replaced.empty()) {
548 				return;
549 			}
550 
551 			if ((unsigned)info.st_mtime == aii->second.modified && fpath == aii->second.path) {
552 				// cache found update checksum if wanted
553 				aii->second.updated = true;
554 				if (doChecksum && (aii->second.checksum == 0)) {
555 					aii->second.checksum = GetCRC(fullName);
556 				}
557 				return;
558 			} else {
559 				if (aii->second.updated) {
560 					LOG_L(L_ERROR, "Found a \"%s\" already in \"%s\", ignoring one in \"%s\"", aii->first.c_str(), aii->second.path.c_str(), fpath.c_str());
561 					return;
562 				}
563 
564 				// If we are here, we could have invalid info in the cache
565 				// Force a reread if it is a directory archive (.sdd), as
566 				// st_mtime only reflects changes to the directory itself,
567 				// not the contents.
568 				archiveInfos.erase(aii);
569 			}
570 		}
571 	}
572 
573 	boost::scoped_ptr<IArchive> ar(archiveLoader.OpenArchive(fullName));
574 	if (!ar || !ar->IsOpen()) {
575 		LOG_L(L_WARNING, "Unable to open archive: %s", fullName.c_str());
576 
577 		// record it as broken, so we don't need to look inside everytime
578 		BrokenArchive& ba = brokenArchives[lcfn];
579 		ba.path = fpath;
580 		ba.modified = info.st_mtime;
581 		ba.updated = true;
582 		ba.problem = "Unable to open archive";
583 		return;
584 	}
585 
586 	std::string error;
587 	std::string mapfile;
588 
589 	const bool hasModinfo = ar->FileExists("modinfo.lua");
590 	const bool hasMapinfo = ar->FileExists("mapinfo.lua");
591 
592 
593 	ArchiveInfo ai;
594 	auto& ad = ai.archiveData;
595 	if (hasMapinfo) {
596 		ScanArchiveLua(ar.get(), "mapinfo.lua", ai, error);
597 		if (ad.GetMapFile().empty()) {
598 			LOG_L(L_WARNING, "%s: mapfile isn't set in mapinfo.lua, please set it for faster loading!", fullName.c_str());
599 			mapfile = SearchMapFile(ar.get(), error);
600 		}
601 	} else if (hasModinfo) {
602 		ScanArchiveLua(ar.get(), "modinfo.lua", ai, error);
603 	} else {
604 		mapfile = SearchMapFile(ar.get(), error);
605 	}
606 	CheckCompression(ar.get(), fullName, error);
607 
608 	if (!error.empty()) {
609 		// for some reason, the archive is marked as broken
610 		LOG_L(L_WARNING, "Failed to scan %s (%s)", fullName.c_str(), error.c_str());
611 
612 		// record it as broken, so we don't need to look inside everytime
613 		BrokenArchive& ba = brokenArchives[lcfn];
614 		ba.path = fpath;
615 		ba.modified = info.st_mtime;
616 		ba.updated = true;
617 		ba.problem = error;
618 		return;
619 	}
620 
621 	if (hasMapinfo || !mapfile.empty()) {
622 		// it is a map
623 		if (ad.GetName().empty()) {
624 			// FIXME The name will never be empty, if version is set (see HACK in ArchiveData)
625 			ad.SetInfoItemValueString("name_pure", FileSystem::GetBasename(mapfile));
626 			ad.SetInfoItemValueString("name", FileSystem::GetBasename(mapfile));
627 		}
628 		if (ad.GetMapFile().empty()) {
629 			ad.SetInfoItemValueString("mapfile", mapfile);
630 		}
631 
632 		AddDependency(ad.GetDependencies(), "Map Helper v1");
633 		ad.SetInfoItemValueInteger("modType", modtype::map);
634 
635 		LOG_S(LOG_SECTION_ARCHIVESCANNER, "Found new map: %s", ad.GetNameVersioned().c_str());
636 	} else if (hasModinfo) {
637 		// it is a game
638 		if (ad.GetModType() == modtype::primary) {
639 			AddDependency(ad.GetDependencies(), "Spring content v1");
640 		}
641 
642 		LOG_S(LOG_SECTION_ARCHIVESCANNER, "Found new game: %s", ad.GetNameVersioned().c_str());
643 	} else {
644 		// neither a map nor a mod: error
645 		error = "missing modinfo.lua/mapinfo.lua";
646 	}
647 
648 	ai.path = fpath;
649 	ai.modified = info.st_mtime;
650 	ai.origName = fn;
651 	ai.updated = true;
652 	ai.checksum = (doChecksum) ? GetCRC(fullName) : 0;
653 	archiveInfos[lcfn] = ai;
654 }
655 
ScanArchiveLua(IArchive * ar,const std::string & fileName,ArchiveInfo & ai,std::string & err)656 bool CArchiveScanner::ScanArchiveLua(IArchive* ar, const std::string& fileName, ArchiveInfo& ai, std::string& err)
657 {
658 	std::vector<boost::uint8_t> buf;
659 	if (!ar->GetFile(fileName, buf) || buf.empty()) {
660 		err = "Error reading " + fileName + " from " + ar->GetArchiveName();
661 		return false;
662 	}
663 
664 	LuaParser p(std::string((char*)(&buf[0]), buf.size()), SPRING_VFS_MOD);
665 	if (!p.Execute()) {
666 		err = "Error in " + fileName + ": " + p.GetErrorLog();
667 		return false;
668 	}
669 
670 	try {
671 		ai.archiveData = CArchiveScanner::ArchiveData(p.GetRoot(), false);
672 
673 		if (!ai.archiveData.IsValid(err)) {
674 			err = "Error in " + fileName + ": " + err;
675 			return false;
676 		}
677 	} catch (const content_error& contErr) {
678 		err = "Error in " + fileName + ": " + contErr.what();
679 		return false;
680 	}
681 
682 	return true;
683 }
684 
CreateIgnoreFilter(IArchive * ar)685 IFileFilter* CArchiveScanner::CreateIgnoreFilter(IArchive* ar)
686 {
687 	IFileFilter* ignore = IFileFilter::Create();
688 	std::vector<boost::uint8_t> buf;
689 	if (ar->GetFile("springignore.txt", buf) || buf.empty()) {
690 		// this automatically splits lines
691 		ignore->AddRule(std::string((char*)(&buf[0]), buf.size()));
692 	}
693 	return ignore;
694 }
695 
696 
697 /// used below
698 struct CRCPair {
699 	std::string* filename;
700 	unsigned int nameCRC;
701 	unsigned int dataCRC;
702 };
703 
704 
705 
706 /**
707  * Get CRC of the data in the specified archive.
708  * Returns 0 if file could not be opened.
709  */
GetCRC(const std::string & arcName)710 unsigned int CArchiveScanner::GetCRC(const std::string& arcName)
711 {
712 	CRC crc;
713 	std::list<std::string> files;
714 
715 	// Try to open an archive
716 	boost::scoped_ptr<IArchive> ar(archiveLoader.OpenArchive(arcName));
717 	if (!ar) {
718 		return 0; // It wasn't an archive
719 	}
720 
721 	// Load ignore list.
722 	boost::scoped_ptr<IFileFilter> ignore(CreateIgnoreFilter(ar.get()));
723 
724 	// Insert all files to check in lowercase format
725 	for (unsigned fid = 0; fid != ar->NumFiles(); ++fid) {
726 		std::string name;
727 		int size;
728 		ar->FileInfo(fid, name, size);
729 
730 		if (ignore->Match(name)) {
731 			continue;
732 		}
733 
734 		StringToLowerInPlace(name); // case insensitive hash
735 		files.push_back(name);
736 	}
737 
738 	// Sort by FileName
739 	files.sort();
740 
741 	// Push the filenames into a std::vector, cause OMP can better iterate over those
742 	std::vector<CRCPair> crcs;
743 	crcs.reserve(files.size());
744 	CRCPair crcp;
745 	for (std::string& f: files) {
746 		crcp.filename = &f;
747 		crcs.push_back(crcp);
748 	}
749 
750 	// Compute CRCs of the files
751 	// Hint: Multithreading only speedups `.sdd` loading. For those the CRC generation is extremely slow -
752 	//       it has to load the full file to calc it! For the other formats (sd7, sdz, sdp) the CRC is saved
753 	//       in the metainformation of the container and so the loading is much faster. Neither does any of our
754 	//       current (2011) packing libraries support multithreading :/
755 	for_mt(0, crcs.size(), [&](const int i) {
756 		CRCPair& crcp = crcs[i];
757 		const unsigned int nameCRC = CRC::GetCRC(crcp.filename->data(), crcp.filename->size());
758 		const unsigned fid = ar->FindFile(*crcp.filename);
759 		const unsigned int dataCRC = ar->GetCrc32(fid);
760 		crcp.nameCRC = nameCRC;
761 		crcp.dataCRC = dataCRC;
762 	#if !defined(DEDICATED) && !defined(UNITSYNC)
763 		Watchdog::ClearTimer(WDT_MAIN);
764 	#endif
765 	});
766 
767 	// Add file CRCs to the main archive CRC
768 	for (CRCPair& crcp: crcs) {
769 		crc.Update(crcp.nameCRC);
770 		crc.Update(crcp.dataCRC);
771 	#if !defined(DEDICATED) && !defined(UNITSYNC)
772 		Watchdog::ClearTimer();
773 	#endif
774 	}
775 
776 	// A value of 0 is used to indicate no crc.. so never return that
777 	// Shouldn't happen all that often
778 	unsigned int digest = crc.GetDigest();
779 	if (digest == 0) digest = 4711;
780 	return digest;
781 }
782 
ReadCacheData(const std::string & filename)783 void CArchiveScanner::ReadCacheData(const std::string& filename)
784 {
785 	if (!FileSystem::FileExists(filename)) {
786 		LOG_L(L_INFO, "Archive cache doesn't exist: %s", filename.c_str());
787 		return;
788 	}
789 
790 	LuaParser p(filename, SPRING_VFS_RAW, SPRING_VFS_BASE);
791 	if (!p.Execute()) {
792 		LOG_L(L_ERROR, "Failed to parse archive cache: %s", p.GetErrorLog().c_str());
793 		return;
794 	}
795 	const LuaTable archiveCache = p.GetRoot();
796 
797 	// Do not load old version caches
798 	const int ver = archiveCache.GetInt("internalVer", (INTERNAL_VER + 1));
799 	if (ver != INTERNAL_VER) {
800 		return;
801 	}
802 
803 	const LuaTable archives = archiveCache.SubTable("archives");
804 	for (int i = 1; archives.KeyExists(i); ++i) {
805 		const LuaTable curArchive = archives.SubTable(i);
806 		const LuaTable archived = curArchive.SubTable("archivedata");
807 		std::string name = curArchive.GetString("name", "");
808 
809 		ArchiveInfo& ai = archiveInfos[StringToLower(name)];
810 		ai.origName = name;
811 		ai.path     = curArchive.GetString("path", "");
812 
813 		// do not use LuaTable.GetInt() for 32-bit integers, the Spring lua
814 		// library uses 32-bit floats to represent numbers, which can only
815 		// represent 2^24 consecutive integers
816 		ai.modified = strtoul(curArchive.GetString("modified", "0").c_str(), 0, 10);
817 		ai.checksum = strtoul(curArchive.GetString("checksum", "0").c_str(), 0, 10);
818 		ai.updated = false;
819 
820 		ai.archiveData = CArchiveScanner::ArchiveData(archived, true);
821 		if (ai.archiveData.GetModType() == modtype::map) {
822 			AddDependency(ai.archiveData.GetDependencies(), "Map Helper v1");
823 		} else if (ai.archiveData.GetModType() == modtype::primary) {
824 			AddDependency(ai.archiveData.GetDependencies(), "Spring content v1");
825 		}
826 	}
827 
828 	const LuaTable brokenArchives = archiveCache.SubTable("brokenArchives");
829 	for (int i = 1; brokenArchives.KeyExists(i); ++i) {
830 		const LuaTable curArchive = brokenArchives.SubTable(i);
831 		std::string name = curArchive.GetString("name", "");
832 		StringToLowerInPlace(name);
833 
834 		BrokenArchive& ba = this->brokenArchives[name];
835 		ba.path = curArchive.GetString("path", "");
836 		ba.modified = strtoul(curArchive.GetString("modified", "0").c_str(), 0, 10);
837 		ba.updated = false;
838 		ba.problem = curArchive.GetString("problem", "unknown");
839 	}
840 
841 	isDirty = false;
842 }
843 
SafeStr(FILE * out,const char * prefix,const std::string & str)844 static inline void SafeStr(FILE* out, const char* prefix, const std::string& str)
845 {
846 	if (str.empty()) {
847 		return;
848 	}
849 	if ( (str.find_first_of("\\\"") != std::string::npos) || (str.find_first_of("\n") != std::string::npos )) {
850 		fprintf(out, "%s[[%s]],\n", prefix, str.c_str());
851 	} else {
852 		fprintf(out, "%s\"%s\",\n", prefix, str.c_str());
853 	}
854 }
855 
FilterDep(std::vector<std::string> & deps,const std::string & exclude)856 void FilterDep(std::vector<std::string>& deps, const std::string& exclude)
857 {
858 	auto it = std::remove_if(deps.begin(), deps.end(), [&](const std::string& dep) { return (dep == exclude); });
859 	deps.erase(it, deps.end());
860 }
861 
WriteCacheData(const std::string & filename)862 void CArchiveScanner::WriteCacheData(const std::string& filename)
863 {
864 	if (!isDirty) {
865 		return;
866 	}
867 
868 	FILE* out = fopen(filename.c_str(), "wt");
869 	if (!out) {
870 		LOG_L(L_ERROR, "Failed to write to \"%s\"!", filename.c_str());
871 		return;
872 	}
873 
874 	// First delete all outdated information
875 	// TODO: this pattern should be moved into an utility function..
876 	for (std::map<std::string, ArchiveInfo>::iterator i = archiveInfos.begin(); i != archiveInfos.end(); ) {
877 		if (!i->second.updated) {
878 			i = set_erase(archiveInfos, i);
879 		} else {
880 			++i;
881 		}
882 	}
883 	for (std::map<std::string, BrokenArchive>::iterator i = brokenArchives.begin(); i != brokenArchives.end(); ) {
884 		if (!i->second.updated) {
885 			i = set_erase(brokenArchives, i);
886 		} else {
887 			++i;
888 		}
889 	}
890 
891 	fprintf(out, "local archiveCache = {\n\n");
892 	fprintf(out, "\tinternalver = %i,\n\n", INTERNAL_VER);
893 	fprintf(out, "\tarchives = {  -- count = " _STPF_ "\n", archiveInfos.size());
894 
895 	std::map<std::string, ArchiveInfo>::const_iterator arcIt;
896 	for (arcIt = archiveInfos.begin(); arcIt != archiveInfos.end(); ++arcIt) {
897 		const ArchiveInfo& arcInfo = arcIt->second;
898 
899 		fprintf(out, "\t\t{\n");
900 		SafeStr(out, "\t\t\tname = ",              arcInfo.origName);
901 		SafeStr(out, "\t\t\tpath = ",              arcInfo.path);
902 		fprintf(out, "\t\t\tmodified = \"%u\",\n", arcInfo.modified);
903 		fprintf(out, "\t\t\tchecksum = \"%u\",\n", arcInfo.checksum);
904 		SafeStr(out, "\t\t\treplaced = ",          arcInfo.replaced);
905 
906 		// mod info?
907 		const ArchiveData& archData = arcInfo.archiveData;
908 		if (!archData.GetName().empty()) {
909 			fprintf(out, "\t\t\tarchivedata = {\n");
910 
911 			const std::map<std::string, InfoItem>& info = archData.GetInfo();
912 			std::map<std::string, InfoItem>::const_iterator ii;
913 			for (ii = info.begin(); ii != info.end(); ++ii) {
914 				switch (ii->second.valueType) {
915 					case INFO_VALUE_TYPE_STRING: {
916 						SafeStr(out, std::string("\t\t\t\t" + ii->first + " = ").c_str(), ii->second.valueTypeString);
917 					} break;
918 					case INFO_VALUE_TYPE_INTEGER: {
919 						fprintf(out, "\t\t\t\t%s = %d,\n", ii->first.c_str(), ii->second.value.typeInteger);
920 					} break;
921 					case INFO_VALUE_TYPE_FLOAT: {
922 						fprintf(out, "\t\t\t\t%s = %f,\n", ii->first.c_str(), ii->second.value.typeFloat);
923 					} break;
924 					case INFO_VALUE_TYPE_BOOL: {
925 						fprintf(out, "\t\t\t\t%s = %d,\n", ii->first.c_str(), (int)ii->second.value.typeBool);
926 					} break;
927 				}
928 			}
929 
930 			std::vector<std::string> deps = archData.GetDependencies();
931 			if (archData.GetModType() == modtype::map) {
932 				FilterDep(deps, "Map Helper v1");
933 			} else if (archData.GetModType() == modtype::primary) {
934 				FilterDep(deps, "Spring content v1");
935 			}
936 
937 			if (!deps.empty()) {
938 				fprintf(out, "\t\t\t\tdepend = {\n");
939 				for (unsigned d = 0; d < deps.size(); d++) {
940 					SafeStr(out, "\t\t\t\t\t", deps[d]);
941 				}
942 				fprintf(out, "\t\t\t\t},\n");
943 			}
944 			fprintf(out, "\t\t\t},\n");
945 		}
946 
947 		fprintf(out, "\t\t},\n");
948 	}
949 
950 	fprintf(out, "\t},\n\n"); // close 'archives'
951 
952 	fprintf(out, "\tbrokenArchives = {  -- count = " _STPF_ "\n", brokenArchives.size());
953 
954 	std::map<std::string, BrokenArchive>::const_iterator bai;
955 	for (bai = brokenArchives.begin(); bai != brokenArchives.end(); ++bai) {
956 		const BrokenArchive& ba = bai->second;
957 
958 		fprintf(out, "\t\t{\n");
959 		SafeStr(out, "\t\t\tname = ", bai->first);
960 		SafeStr(out, "\t\t\tpath = ", ba.path);
961 		fprintf(out, "\t\t\tmodified = \"%u\",\n", ba.modified);
962 		SafeStr(out, "\t\t\tproblem = ", ba.problem);
963 		fprintf(out, "\t\t},\n");
964 	}
965 
966 	fprintf(out, "\t},\n"); // close 'brokenArchives'
967 
968 	fprintf(out, "}\n\n"); // close 'archiveCache'
969 	fprintf(out, "return archiveCache\n");
970 
971 	if (fclose(out) == EOF)
972 		LOG_L(L_ERROR, "Failed to write to \"%s\"!", filename.c_str());
973 
974 	isDirty = false;
975 }
976 
977 
archNameCompare(const CArchiveScanner::ArchiveData & a,const CArchiveScanner::ArchiveData & b)978 static bool archNameCompare(const CArchiveScanner::ArchiveData& a, const CArchiveScanner::ArchiveData& b)
979 {
980 	return (a.GetNameVersioned() < b.GetNameVersioned());
981 }
sortByName(std::vector<CArchiveScanner::ArchiveData> & data)982 static void sortByName(std::vector<CArchiveScanner::ArchiveData>& data)
983 {
984 	std::sort(data.begin(), data.end(), archNameCompare);
985 }
986 
GetPrimaryMods() const987 std::vector<CArchiveScanner::ArchiveData> CArchiveScanner::GetPrimaryMods() const
988 {
989 	std::vector<ArchiveData> ret;
990 
991 	for (std::map<std::string, ArchiveInfo>::const_iterator i = archiveInfos.begin(); i != archiveInfos.end(); ++i) {
992 		const ArchiveData& aid = i->second.archiveData;
993 		if ((!aid.GetName().empty()) && (aid.GetModType() == modtype::primary)) {
994 			// Add the archive the mod is in as the first dependency
995 			ArchiveData md = aid;
996 			md.GetDependencies().insert(md.GetDependencies().begin(), i->second.origName);
997 			ret.push_back(md);
998 		}
999 	}
1000 
1001 	sortByName(ret);
1002 	return ret;
1003 }
1004 
1005 
GetAllMods() const1006 std::vector<CArchiveScanner::ArchiveData> CArchiveScanner::GetAllMods() const
1007 {
1008 	std::vector<ArchiveData> ret;
1009 
1010 	for (std::map<std::string, ArchiveInfo>::const_iterator i = archiveInfos.begin(); i != archiveInfos.end(); ++i) {
1011 		const ArchiveData& aid = i->second.archiveData;
1012 		if ((!aid.GetName().empty()) && ((aid.GetModType() & (modtype::primary | modtype::hidden)) != 0)) {
1013 			// Add the archive the mod is in as the first dependency
1014 			ArchiveData md = aid;
1015 			md.GetDependencies().insert(md.GetDependencies().begin(), i->second.origName);
1016 			ret.push_back(md);
1017 		}
1018 	}
1019 
1020 	sortByName(ret);
1021 	return ret;
1022 }
1023 
1024 
GetAllArchives() const1025 std::vector<CArchiveScanner::ArchiveData> CArchiveScanner::GetAllArchives() const
1026 {
1027 	std::vector<ArchiveData> ret;
1028 
1029 	for (const auto& pair: archiveInfos) {
1030 		const ArchiveData& aid = pair.second.archiveData;
1031 
1032 		// Add the archive the mod is in as the first dependency
1033 		ArchiveData md = aid;
1034 		md.GetDependencies().insert(md.GetDependencies().begin(), pair.second.origName);
1035 		ret.push_back(md);
1036 	}
1037 
1038 	sortByName(ret);
1039 	return ret;
1040 }
1041 
GetAllArchivesUsedBy(const std::string & root,int depth) const1042 std::vector<std::string> CArchiveScanner::GetAllArchivesUsedBy(const std::string& root, int depth) const
1043 {
1044 	LOG_S(LOG_SECTION_ARCHIVESCANNER, "GetArchives: %s (depth %u)", root.c_str(), depth);
1045 	// Protect against circular dependencies
1046 	// (worst case depth is if all archives form one huge dependency chain)
1047 	if ((unsigned)depth > archiveInfos.size()) {
1048 		throw content_error("Circular dependency");
1049 	}
1050 
1051 	std::vector<std::string> ret;
1052 	std::string lcname = StringToLower(ArchiveFromName(root));
1053 	std::map<std::string, ArchiveInfo>::const_iterator aii = archiveInfos.find(lcname);
1054 	if (aii == archiveInfos.end()) {
1055 #ifdef UNITSYNC
1056 		// unresolved dep, add it, so unitsync still shows this file
1057 		ret.push_back(lcname);
1058 		return ret;
1059 #else
1060 		throw content_error("Archive \"" + lcname + "\" not found");
1061 #endif
1062 	}
1063 
1064 	// Check if this archive has been replaced
1065 	while (aii->second.replaced.length() > 0) {
1066 		// FIXME instead of this, call this function recursively, to get the propper error handling
1067 		aii = archiveInfos.find(aii->second.replaced);
1068 		if (aii == archiveInfos.end()) {
1069 #ifdef UNITSYNC
1070 			// unresolved dep, add it, so unitsync still shows this file
1071 			ret.push_back(lcname);
1072 			return ret;
1073 #else
1074 			throw content_error("Unknown error parsing archive replacements");
1075 #endif
1076 		}
1077 	}
1078 
1079 	// add depth-first
1080 	ret.push_back(aii->second.path + aii->second.origName);
1081 	for (const std::string& dep: aii->second.archiveData.GetDependencies()) {
1082 		const std::vector<std::string>& deps = GetAllArchivesUsedBy(dep, depth + 1);
1083 		for (const std::string& depSub: deps) {
1084 			AddDependency(ret, depSub);
1085 		}
1086 	}
1087 
1088 	return ret;
1089 }
1090 
1091 
GetMaps() const1092 std::vector<std::string> CArchiveScanner::GetMaps() const
1093 {
1094 	std::vector<std::string> ret;
1095 
1096 	for (std::map<std::string, ArchiveInfo>::const_iterator aii = archiveInfos.begin(); aii != archiveInfos.end(); ++aii) {
1097 		if (!(aii->second.archiveData.GetName().empty()) && aii->second.archiveData.GetModType() == modtype::map) {
1098 			ret.push_back(aii->second.archiveData.GetNameVersioned());
1099 		}
1100 	}
1101 
1102 	return ret;
1103 }
1104 
MapNameToMapFile(const std::string & s) const1105 std::string CArchiveScanner::MapNameToMapFile(const std::string& s) const
1106 {
1107 	// Convert map name to map archive
1108 	for (std::map<std::string, ArchiveInfo>::const_iterator aii = archiveInfos.begin(); aii != archiveInfos.end(); ++aii) {
1109 		if (s == aii->second.archiveData.GetNameVersioned()) {
1110 			return aii->second.archiveData.GetMapFile();
1111 		}
1112 	}
1113 	LOG_SL(LOG_SECTION_ARCHIVESCANNER, L_WARNING, "map file of %s not found", s.c_str());
1114 	return s;
1115 }
1116 
GetSingleArchiveChecksum(const std::string & name) const1117 unsigned int CArchiveScanner::GetSingleArchiveChecksum(const std::string& name) const
1118 {
1119 	std::string lcname = FileSystem::GetFilename(name);
1120 	StringToLowerInPlace(lcname);
1121 
1122 	std::map<std::string, ArchiveInfo>::const_iterator aii = archiveInfos.find(lcname);
1123 	if (aii == archiveInfos.end()) {
1124 		LOG_SL(LOG_SECTION_ARCHIVESCANNER, L_WARNING, "%s checksum: not found (0)", name.c_str());
1125 		return 0;
1126 	}
1127 
1128 	LOG_S(LOG_SECTION_ARCHIVESCANNER,"%s checksum: %d/%u", name.c_str(), aii->second.checksum, aii->second.checksum);
1129 	return aii->second.checksum;
1130 }
1131 
GetArchiveCompleteChecksum(const std::string & name) const1132 unsigned int CArchiveScanner::GetArchiveCompleteChecksum(const std::string& name) const
1133 {
1134 	const std::vector<std::string>& ars = GetAllArchivesUsedBy(name);
1135 	unsigned int checksum = 0;
1136 
1137 	for (unsigned int a = 0; a < ars.size(); a++) {
1138 		checksum ^= GetSingleArchiveChecksum(ars[a]);
1139 	}
1140 	LOG_S(LOG_SECTION_ARCHIVESCANNER, "archive checksum %s: %d/%u", name.c_str(), checksum, checksum);
1141 	return checksum;
1142 }
1143 
CheckArchive(const std::string & name,unsigned checksum) const1144 void CArchiveScanner::CheckArchive(const std::string& name, unsigned checksum) const
1145 {
1146 	unsigned localChecksum = GetArchiveCompleteChecksum(name);
1147 
1148 	if (localChecksum != checksum) {
1149 		char msg[1024];
1150 		sprintf(
1151 			msg,
1152 			"Checksum of %s (checksum 0x%x) differs from the host's copy (checksum 0x%x). "
1153 			"This may be caused by a corrupted download or there may even "
1154 			"be 2 different versions in circulation. Make sure you and the host have installed "
1155 			"the chosen archive and its dependencies and consider redownloading it.",
1156 			name.c_str(), localChecksum, checksum);
1157 
1158 		throw content_error(msg);
1159 	}
1160 }
1161 
GetArchivePath(const std::string & name) const1162 std::string CArchiveScanner::GetArchivePath(const std::string& name) const
1163 {
1164 	const std::string lcname = StringToLower(FileSystem::GetFilename(name));
1165 	std::map<std::string, ArchiveInfo>::const_iterator aii = archiveInfos.find(lcname);
1166 	if (aii == archiveInfos.end()) {
1167 		return "";
1168 	}
1169 	return aii->second.path;
1170 }
1171 
ArchiveFromName(const std::string & name) const1172 std::string CArchiveScanner::ArchiveFromName(const std::string& name) const
1173 {
1174 	for (std::map<std::string, ArchiveInfo>::const_iterator it = archiveInfos.begin(); it != archiveInfos.end(); ++it) {
1175 		if (it->second.archiveData.GetNameVersioned() == name) {
1176 			return it->second.origName;
1177 		}
1178 	}
1179 
1180 	return name;
1181 }
1182 
NameFromArchive(const std::string & archiveName) const1183 std::string CArchiveScanner::NameFromArchive(const std::string& archiveName) const
1184 {
1185 	const std::string lcArchiveName = StringToLower(archiveName);
1186 	std::map<std::string, ArchiveInfo>::const_iterator aii = archiveInfos.find(lcArchiveName);
1187 	if (aii != archiveInfos.end()) {
1188 		return aii->second.archiveData.GetNameVersioned();
1189 	}
1190 	return archiveName;
1191 }
1192 
GetArchiveData(const std::string & name) const1193 CArchiveScanner::ArchiveData CArchiveScanner::GetArchiveData(const std::string& name) const
1194 {
1195 	for (std::map<std::string, ArchiveInfo>::const_iterator it = archiveInfos.begin(); it != archiveInfos.end(); ++it) {
1196 		const ArchiveData& md = it->second.archiveData;
1197 		if (md.GetNameVersioned() == name) {
1198 			return md;
1199 		}
1200 	}
1201 	return ArchiveData();
1202 }
1203 
GetArchiveDataByArchive(const std::string & archive) const1204 CArchiveScanner::ArchiveData CArchiveScanner::GetArchiveDataByArchive(const std::string& archive) const
1205 {
1206 	return GetArchiveData(NameFromArchive(archive));
1207 }
1208 
GetMetaFileClass(const std::string & filePath)1209 unsigned char CArchiveScanner::GetMetaFileClass(const std::string& filePath)
1210 {
1211 
1212 	unsigned char metaFileClass = 0;
1213 
1214 	const std::string lowerFilePath = StringToLower(filePath);
1215 	const std::string ext = FileSystem::GetExtension(lowerFilePath);
1216 
1217 	// 1: what is commonly read from all archives when scanning through them
1218 	// 2: what is less commoonly used, or only used when looking
1219 	//    at a specific archive (for example when hosting Game-X)
1220 	if (lowerFilePath == "mapinfo.lua") {                      // basic archive info
1221 		metaFileClass = 1;
1222 	} else if (lowerFilePath == "modinfo.lua") {               // basic archive info
1223 		metaFileClass = 1;
1224 //	} else if ((ext == "smf") || (ext == "sm3")) {             // to generate minimap
1225 //		metaFileClass = 1;
1226 	} else if (lowerFilePath == "modoptions.lua") {            // used by lobbies
1227 		metaFileClass = 2;
1228 	} else if (lowerFilePath == "engineoptions.lua") {         // used by lobbies
1229 		metaFileClass = 2;
1230 	} else if (lowerFilePath == "validmaps.lua") {             // used by lobbies
1231 		metaFileClass = 2;
1232 	} else if (lowerFilePath == "luaai.lua") {                 // used by lobbies
1233 		metaFileClass = 2;
1234 	} else if (StringStartsWith(lowerFilePath, "sidepics/")) { // used by lobbies
1235 		metaFileClass = 2;
1236 	} else if (StringStartsWith(lowerFilePath, "gamedata/")) { // used by lobbies
1237 		metaFileClass = 2;
1238 	} else if (lowerFilePath == "armor.txt") {                 // used by lobbies (disabled units list)
1239 		metaFileClass = 2;
1240 	} else if (lowerFilePath == "springignore.txt") {          // used by lobbies (disabled units list)
1241 		metaFileClass = 2;
1242 	} else if (StringStartsWith(lowerFilePath, "units/")) {    // used by lobbies (disabled units list)
1243 		metaFileClass = 2;
1244 	} else if (StringStartsWith(lowerFilePath, "features/")) { // used by lobbies (disabled units list)
1245 		metaFileClass = 2;
1246 	} else if (StringStartsWith(lowerFilePath, "weapons/")) {  // used by lobbies (disabled units list)
1247 		metaFileClass = 2;
1248 	}
1249 	// Lobbies get the unit list from unitsync. Unitsync gets it by executing
1250 	// gamedata/defs.lua, which loads units, features, weapons, move types
1251 	// and armors (that is why armor.txt is in the list).
1252 
1253 	return metaFileClass;
1254 }
1255 
1256