std::vector<std::string> CArchiveScanner::GetArchives(const std::string& root, int depth) const { LOG_S(LOG_SECTION_ARCHIVESCANNER, "GetArchives: %s (depth %u)", root.c_str(), depth); // Protect against circular dependencies // (worst case depth is if all archives form one huge dependency chain) if ((unsigned)depth > archiveInfos.size()) { throw content_error("Circular dependency"); } std::vector<std::string> ret; std::string lcname = StringToLower(ArchiveFromName(root)); std::map<std::string, ArchiveInfo>::const_iterator aii = archiveInfos.find(lcname); if (aii == archiveInfos.end()) { #ifdef UNITSYNC // unresolved dep, add it, so unitsync still shows this file if (!ret.empty()) { ret.push_back(lcname); } return ret; #else throw content_error("Archive \"" + lcname + "\" not found"); #endif } // Check if this archive has been replaced while (aii->second.replaced.length() > 0) { // FIXME instead of this, call this function recursively, to get the propper error handling aii = archiveInfos.find(aii->second.replaced); if (aii == archiveInfos.end()) { throw content_error("Unknown error parsing archive replacements"); } } ret.push_back(aii->second.path + aii->second.origName); // add depth-first for (std::vector<std::string>::const_iterator i = aii->second.archiveData.GetDependencies().begin(); i != aii->second.archiveData.GetDependencies().end(); ++i) { const std::vector<std::string>& deps = GetArchives(*i, depth + 1); for (std::vector<std::string>::const_iterator j = deps.begin(); j != deps.end(); ++j) { if (std::find(ret.begin(), ret.end(), *j) == ret.end()) { // add only if this dependency is not already somewhere // in the chain (which can happen if ArchiveCache.lua has // not been written yet) so its checksum is not XOR'ed // with the running one multiple times (Get*Checksum()) ret.push_back(*j); } } } return ret; }
unsigned int CArchiveScanner::GetArchiveCompleteChecksum(const std::string& name) { const std::vector<std::string> ars = GetAllArchivesUsedBy(name); unsigned int checksum = 0; for (const std::string& depName: ars) { const std::string& archive = ArchiveFromName(depName); checksum ^= GetSingleArchiveChecksum(GetArchivePath(archive) + archive); } LOG_S(LOG_SECTION_ARCHIVESCANNER, "archive checksum %s: %d/%u", name.c_str(), checksum, checksum); return checksum; }
std::vector<std::string> CArchiveScanner::GetArchives(const std::string& root, int depth) const { logOutput.Print(LOG_ARCHIVESCANNER, "GetArchives: %s (depth %u)", root.c_str(), depth); //! Protect against circular dependencies //! (worst case depth is if all archives form one huge dependency chain) if ((unsigned)depth > archiveInfo.size()) { throw content_error("Circular dependency"); } std::vector<std::string> ret; std::string lcname = StringToLower(ArchiveFromName(root)); std::map<std::string, ArchiveInfo>::const_iterator aii = archiveInfo.find(lcname); if (aii == archiveInfo.end()) { //! unresolved dep if (!ret.empty()) { //! add anyway so we get propper error-handling (only when it is not the main-archive!) ret.push_back(lcname); } return ret; } //! Check if this archive has been replaced while (aii->second.replaced.length() > 0) { aii = archiveInfo.find(aii->second.replaced); if (aii == archiveInfo.end()) { throw content_error("Unknown error parsing archive replacements"); } } ret.push_back(aii->second.path + aii->second.origName); //! add depth-first for (std::vector<std::string>::const_iterator i = aii->second.archiveData.GetDependencies().begin(); i != aii->second.archiveData.GetDependencies().end(); ++i) { const std::vector<std::string>& dep = GetArchives(*i, depth + 1); for (std::vector<std::string>::const_iterator j = dep.begin(); j != dep.end(); ++j) { if (std::find(ret.begin(), ret.end(), *j) == ret.end()) { //! add only if this dependency is not already somewhere //! in the chain (which can happen if ArchiveCache.lua has //! not been written yet) so its checksum is not XOR'ed //! with the running one multiple times (Get*Checksum()) ret.push_back(*j); } } } return ret; }
std::vector<std::string> CArchiveScanner::GetAllArchivesUsedBy(const std::string& root, int depth) const { LOG_S(LOG_SECTION_ARCHIVESCANNER, "GetArchives: %s (depth %u)", root.c_str(), depth); // Protect against circular dependencies // (worst case depth is if all archives form one huge dependency chain) if ((unsigned)depth > archiveInfos.size()) { throw content_error("Circular dependency"); } std::vector<std::string> ret; std::string resolvedName = ArchiveNameResolver::GetGame(root); std::string lcname = StringToLower(ArchiveFromName(resolvedName)); std::map<std::string, ArchiveInfo>::const_iterator aii = archiveInfos.find(lcname); if (aii == archiveInfos.end()) { #ifdef UNITSYNC // unresolved dep, add it, so unitsync still shows this file ret.push_back(lcname); return ret; #else throw content_error("Archive \"" + lcname + "\" not found"); #endif } // Check if this archive has been replaced while (aii->second.replaced.length() > 0) { // FIXME instead of this, call this function recursively, to get the propper error handling aii = archiveInfos.find(aii->second.replaced); if (aii == archiveInfos.end()) { #ifdef UNITSYNC // unresolved dep, add it, so unitsync still shows this file ret.push_back(lcname); return ret; #else throw content_error("Unknown error parsing archive replacements"); #endif } } // add depth-first ret.push_back(aii->second.path + aii->second.origName); for (const std::string& dep: aii->second.archiveData.GetDependencies()) { const std::vector<std::string>& deps = GetAllArchivesUsedBy(dep, depth + 1); for (const std::string& depSub: deps) { AddDependency(ret, depSub); } } return ret; }
std::vector<std::string> CArchiveScanner::GetAllArchivesUsedBy(const std::string& rootArchive) const { LOG_S(LOG_SECTION_ARCHIVESCANNER, "GetArchives: %s", rootArchive.c_str()); // VectorInsertUnique'ing via AddDependency can become a performance hog // for very long dependency chains, prefer to sort and remove duplicates const auto& NameCmp = [](const std::pair<std::string, size_t>& a, const std::pair<std::string, size_t>& b) { return (a.first < b.first ); }; const auto& IndxCmp = [](const std::pair<std::string, size_t>& a, const std::pair<std::string, size_t>& b) { return (a.second < b.second); }; std::vector< std::string > retArchives; std::vector<std::pair<std::string, size_t>> tmpArchives[2]; std::deque<std::string> archiveQueue = {rootArchive}; retArchives.reserve(8); tmpArchives[0].reserve(8); tmpArchives[1].reserve(8); while (!archiveQueue.empty()) { // protect against circular dependencies; worst case is if all archives form one huge chain if (archiveQueue.size() > archiveInfos.size()) break; const std::string& resolvedName = ArchiveNameResolver::GetGame(archiveQueue.front()); const std::string& lowerCaseName = StringToLower(ArchiveFromName(resolvedName)); archiveQueue.pop_front(); const ArchiveInfo* archiveInfo = nullptr; const auto CanAddSubDependencies = [&](const std::string& lwrCaseName) -> const ArchiveInfo* { #ifdef UNITSYNC // add unresolved deps for unitsync so it still shows this file const auto HandleUnresolvedDep = [&tmpArchives](const std::string& archName) { tmpArchives[0].emplace_back(archName, tmpArchives[0].size()); return true; }; #else const auto HandleUnresolvedDep = [&tmpArchives](const std::string& archName) { (void) archName; return false; }; #endif auto aii = archiveInfos.find(lwrCaseName); auto aij = aii; const ArchiveInfo* ai = nullptr; if (aii == archiveInfos.end()) { if (!HandleUnresolvedDep(lwrCaseName)) throw content_error("Archive \"" + lwrCaseName + "\" not found"); return nullptr; } ai = &aii->second; // check if this archive has an unresolved replacement while (!ai->replaced.empty()) { if ((aii = archiveInfos.find(ai->replaced)) == archiveInfos.end()) { if (!HandleUnresolvedDep(lwrCaseName)) throw content_error("Replacement \"" + ai->replaced + "\" for archive \"" + lwrCaseName + "\" not found"); return nullptr; } aij = aii; ai = &aij->second; } return ai; }; if ((archiveInfo = CanAddSubDependencies(lowerCaseName)) == nullptr) continue; tmpArchives[0].emplace_back(archiveInfo->archiveData.GetNameVersioned(), tmpArchives[0].size()); // expand dependencies in depth-first order for (const std::string& archiveDep: archiveInfo->archiveData.GetDependencies()) { assert(archiveDep != rootArchive); assert(archiveDep != tmpArchives[0][tmpArchives[0].size() - 1].first); archiveQueue.push_front(archiveDep); } } std::stable_sort(tmpArchives[0].begin(), tmpArchives[0].end(), NameCmp); // filter out any duplicate dependencies for (auto& archiveEntry: tmpArchives[0]) { if (tmpArchives[1].empty() || archiveEntry.first != tmpArchives[1][tmpArchives[1].size() - 1].first) { tmpArchives[1].emplace_back(std::move(archiveEntry.first), archiveEntry.second); } } // resort in original traversal order so overrides work as expected std::stable_sort(tmpArchives[1].begin(), tmpArchives[1].end(), IndxCmp); for (auto& archiveEntry: tmpArchives[1]) { retArchives.emplace_back(std::move(archiveEntry.first)); } return retArchives; }