#include "DataSpec/DNACommon/PAK.hpp" #include "DataSpec/DNAMP1/DNAMP1.hpp" #include "DataSpec/DNAMP2/DNAMP2.hpp" #include "DataSpec/DNAMP3/DNAMP3.hpp" namespace DataSpec { template void UniqueResult::checkEntry(const PAKBRIDGE& pakBridge, const typename PAKBRIDGE::PAKType::Entry& entry) { UniqueResult::Type resultType = UniqueResult::Type::NotFound; bool foundOneLayer = false; const hecl::SystemString* levelName = nullptr; typename PAKBRIDGE::PAKType::IDType useLevelId; typename PAKBRIDGE::PAKType::IDType useAreaId; unsigned layerIdx = 0; for (const auto& [levelId, level] : pakBridge.m_levelDeps) { if (entry.id == levelId || level.resources.find(entry.id) != level.resources.end()) { levelName = &level.name; resultType = UniqueResult::Type::Level; break; } for (const auto& [areaId, area] : level.areas) { unsigned l = 0; for (const auto& layer : area.layers) { if (layer.resources.find(entry.id) != layer.resources.end()) { if (foundOneLayer) { if (useAreaId == areaId) { resultType = UniqueResult::Type::Area; } else if (useLevelId == levelId) { resultType = UniqueResult::Type::Level; break; } else { m_type = UniqueResult::Type::Pak; return; } continue; } else resultType = UniqueResult::Type::Layer; levelName = &level.name; useLevelId = levelId; useAreaId = areaId; layerIdx = l; foundOneLayer = true; } ++l; } if (area.resources.find(entry.id) != area.resources.end()) { if (foundOneLayer) { if (useAreaId == areaId) { resultType = UniqueResult::Type::Area; } else if (useLevelId == levelId) { resultType = UniqueResult::Type::Level; break; } else { m_type = UniqueResult::Type::Pak; return; } continue; } else resultType = UniqueResult::Type::Area; levelName = &level.name; useLevelId = levelId; useAreaId = areaId; foundOneLayer = true; } } } m_type = resultType; m_levelName = levelName; if (resultType == UniqueResult::Type::Layer || resultType == UniqueResult::Type::Area) { const typename PAKBRIDGE::Level::Area& area = pakBridge.m_levelDeps.at(useLevelId).areas.at(useAreaId); m_areaName = &area.name; if (resultType == UniqueResult::Type::Layer) { const typename PAKBRIDGE::Level::Area::Layer& layer = area.layers[layerIdx]; m_layerName = &layer.name; } } } template void UniqueResult::checkEntry(const DNAMP1::PAKBridge& pakBridge, const DNAMP1::PAKBridge::PAKType::Entry& entry); template void UniqueResult::checkEntry(const DNAMP2::PAKBridge& pakBridge, const DNAMP2::PAKBridge::PAKType::Entry& entry); template void UniqueResult::checkEntry(const DNAMP3::PAKBridge& pakBridge, const DNAMP3::PAKBridge::PAKType::Entry& entry); hecl::ProjectPath UniqueResult::uniquePath(const hecl::ProjectPath& pakPath) const { if (m_type == Type::Pak) return pakPath; hecl::ProjectPath levelDir; if (m_levelName) levelDir.assign(pakPath, *m_levelName); else levelDir = pakPath; if (m_type == Type::Area) { hecl::ProjectPath areaDir(levelDir, *m_areaName); return areaDir; } else if (m_type == Type::Layer) { hecl::ProjectPath areaDir(levelDir, *m_areaName); hecl::ProjectPath layerDir(areaDir, *m_layerName); return layerDir; } return levelDir; } template void PAKRouter::build(std::vector& bridges, std::function progress) { m_bridges = &bridges; m_bridgePaths.clear(); m_uniqueEntries.clear(); m_sharedEntries.clear(); m_charAssoc.m_cmdlRigs.clear(); size_t count = 0; float bridgesSz = bridges.size(); /* Route entries unique/shared per-pak */ size_t bridgeIdx = 0; for (BRIDGETYPE& bridge : bridges) { const auto& name = bridge.getName(); hecl::SystemStringConv sysName(name); hecl::SystemStringView::const_iterator extit = sysName.sys_str().end() - 4; hecl::SystemString baseName(sysName.sys_str().begin(), extit); m_bridgePaths.emplace_back( std::make_pair(hecl::ProjectPath(m_gameWorking, baseName), hecl::ProjectPath(m_gameCooked, baseName))); /* Index this PAK */ bridge.build(); /* Add to global entry lookup */ const typename BRIDGETYPE::PAKType& pak = bridge.getPAK(); for (const auto& entry : pak.m_entries) { if (!pak.m_noShare) { auto sSearch = m_sharedEntries.find(entry.first); if (sSearch != m_sharedEntries.end()) continue; auto uSearch = m_uniqueEntries.find(entry.first); if (uSearch != m_uniqueEntries.end()) { m_uniqueEntries.erase(uSearch); m_sharedEntries[entry.first] = std::make_pair(bridgeIdx, &entry.second); } else m_uniqueEntries[entry.first] = std::make_pair(bridgeIdx, &entry.second); } else m_uniqueEntries[entry.first] = std::make_pair(bridgeIdx, &entry.second); } /* Add RigPairs to global map */ bridge.addCMDLRigPairs(*this, m_charAssoc); progress(++count / bridgesSz); ++bridgeIdx; } /* Add named resources to catalog YAML files */ for (BRIDGETYPE& bridge : bridges) { athena::io::YAMLDocWriter catalogWriter; enterPAKBridge(bridge); /* Add MAPA transforms to global map */ bridge.addMAPATransforms(*this, m_mapaTransforms, m_overrideEntries); const typename BRIDGETYPE::PAKType& pak = bridge.getPAK(); for (const auto& namedEntry : pak.m_nameEntries) { if (namedEntry.name == "holo_cinf") continue; /* Problematic corner case */ if (auto rec = catalogWriter.enterSubRecord(namedEntry.name)) { hecl::ProjectPath working = getWorking(namedEntry.id); if (working.getAuxInfoUTF8().size()) { if (auto v = catalogWriter.enterSubVector()) { catalogWriter.writeString(working.getRelativePathUTF8()); catalogWriter.writeString(working.getAuxInfoUTF8()); } } else catalogWriter.writeString(working.getRelativePathUTF8()); } } /* Write catalog */ intptr_t curBridgeIdx = reinterpret_cast(m_curBridgeIdx.get()); const hecl::ProjectPath& pakPath = m_bridgePaths[curBridgeIdx].first; pakPath.makeDirChain(true); athena::io::FileWriter writer(hecl::ProjectPath(pakPath, "!catalog.yaml").getAbsolutePath()); catalogWriter.finish(&writer); } } template void PAKRouter::enterPAKBridge(const BRIDGETYPE& pakBridge) { g_PakRouter.reset(this); auto pit = m_bridgePaths.begin(); size_t bridgeIdx = 0; for (const BRIDGETYPE& bridge : *m_bridges) { if (&bridge == &pakBridge) { m_pak.reset(&pakBridge.getPAK()); m_node.reset(&pakBridge.getNode()); m_curBridgeIdx.reset(reinterpret_cast(bridgeIdx)); return; } ++pit; ++bridgeIdx; } LogDNACommon.report(logvisor::Fatal, FMT_STRING("PAKBridge provided to PAKRouter::enterPAKBridge() was not part of build()")); } template hecl::ProjectPath PAKRouter::getCharacterWorking(const EntryType* entry) const { auto characterSearch = m_charAssoc.m_cskrToCharacter.find(entry->id); if (characterSearch != m_charAssoc.m_cskrToCharacter.cend()) { hecl::ProjectPath characterPath = getWorking(characterSearch->second.first); if (entry->type == FOURCC('EVNT')) { hecl::SystemStringConv wideStr(characterSearch->second.second); return characterPath.getWithExtension((hecl::SystemString(_SYS_STR(".")) + wideStr.c_str()).c_str(), true); } return characterPath.ensureAuxInfo(characterSearch->second.second); } return {}; } template hecl::ProjectPath PAKRouter::getWorking(const EntryType* entry, const ResExtractor& extractor) const { if (!entry) return hecl::ProjectPath(); auto overrideSearch = m_overrideEntries.find(entry->id); if (overrideSearch != m_overrideEntries.end()) return overrideSearch->second; const PAKType* pak = m_pak.get(); intptr_t curBridgeIdx = reinterpret_cast(m_curBridgeIdx.get()); if (pak && pak->m_noShare) { const EntryType* singleSearch = pak->lookupEntry(entry->id); if (singleSearch) { const hecl::ProjectPath& pakPath = m_bridgePaths[curBridgeIdx].first; hecl::SystemString entName = hecl::UTF8StringToSysString(getBestEntryName(*entry)); hecl::SystemString auxInfo; if (extractor.fileExts[0] && !extractor.fileExts[1]) entName += extractor.fileExts[0]; else if (extractor.fileExts[0]) entName += _SYS_STR(".*"); else if (hecl::ProjectPath chWork = getCharacterWorking(entry)) return chWork; return hecl::ProjectPath(pakPath, entName).ensureAuxInfo(auxInfo); } } auto uniqueSearch = m_uniqueEntries.find(entry->id); if (uniqueSearch != m_uniqueEntries.end()) { const BRIDGETYPE& bridge = m_bridges->at(uniqueSearch->second.first); const hecl::ProjectPath& pakPath = m_bridgePaths[uniqueSearch->second.first].first; hecl::SystemString entName = hecl::UTF8StringToSysString(getBestEntryName(*entry)); hecl::SystemString auxInfo; if (extractor.fileExts[0] && !extractor.fileExts[1]) entName += extractor.fileExts[0]; else if (extractor.fileExts[0]) entName += _SYS_STR(".*"); else if (hecl::ProjectPath chWork = getCharacterWorking(entry)) return chWork; if (bridge.getPAK().m_noShare) { return hecl::ProjectPath(pakPath, entName).ensureAuxInfo(auxInfo); } else { hecl::ProjectPath uniquePath = entry->unique.uniquePath(pakPath); return hecl::ProjectPath(uniquePath, entName).ensureAuxInfo(auxInfo); } } auto sharedSearch = m_sharedEntries.find(entry->id); if (sharedSearch != m_sharedEntries.end()) { hecl::SystemString entBase = hecl::UTF8StringToSysString(getBestEntryName(*entry)); hecl::SystemString auxInfo; hecl::SystemString entName = entBase; if (extractor.fileExts[0] && !extractor.fileExts[1]) entName += extractor.fileExts[0]; else if (extractor.fileExts[0]) entName += _SYS_STR(".*"); else if (hecl::ProjectPath chWork = getCharacterWorking(entry)) return chWork; hecl::ProjectPath sharedPath(m_sharedWorking, entName); return sharedPath.ensureAuxInfo(auxInfo); } LogDNACommon.report(logvisor::Fatal, FMT_STRING("Unable to find entry {}"), entry->id); return hecl::ProjectPath(); } template hecl::ProjectPath PAKRouter::getWorking(const EntryType* entry) const { if (!entry) return hecl::ProjectPath(); return getWorking(entry, BRIDGETYPE::LookupExtractor(*m_node.get(), *m_pak.get(), *entry)); } template hecl::ProjectPath PAKRouter::getWorking(const IDType& id, bool silenceWarnings) const { return getWorking(lookupEntry(id, nullptr, silenceWarnings, false)); } template hecl::ProjectPath PAKRouter::getCooked(const EntryType* entry) const { if (!entry) return hecl::ProjectPath(); auto overrideSearch = m_overrideEntries.find(entry->id); if (overrideSearch != m_overrideEntries.end()) { return overrideSearch->second.getCookedPath(*m_dataSpec.overrideDataSpec( overrideSearch->second, m_dataSpec.getDataSpecEntry())); } const PAKType* pak = m_pak.get(); intptr_t curBridgeIdx = reinterpret_cast(m_curBridgeIdx.get()); if (pak && pak->m_noShare) { const EntryType* singleSearch = pak->lookupEntry(entry->id); if (singleSearch) { const hecl::ProjectPath& pakPath = m_bridgePaths[curBridgeIdx].second; return hecl::ProjectPath(pakPath, getBestEntryName(*entry)); } } auto uniqueSearch = m_uniqueEntries.find(entry->id); if (uniqueSearch != m_uniqueEntries.end()) { const BRIDGETYPE& bridge = m_bridges->at(uniqueSearch->second.first); const hecl::ProjectPath& pakPath = m_bridgePaths[uniqueSearch->second.first].second; if (bridge.getPAK().m_noShare) { return hecl::ProjectPath(pakPath, getBestEntryName(*entry)); } else { hecl::ProjectPath uniquePath = entry->unique.uniquePath(pakPath); return hecl::ProjectPath(uniquePath, getBestEntryName(*entry)); } } auto sharedSearch = m_sharedEntries.find(entry->id); if (sharedSearch != m_sharedEntries.end()) { return hecl::ProjectPath(m_sharedCooked, getBestEntryName(*entry)); } LogDNACommon.report(logvisor::Fatal, FMT_STRING("Unable to find entry {}"), entry->id); return hecl::ProjectPath(); } template hecl::ProjectPath PAKRouter::getCooked(const IDType& id, bool silenceWarnings) const { return getCooked(lookupEntry(id, nullptr, silenceWarnings, false)); } template hecl::SystemString PAKRouter::getResourceRelativePath(const EntryType& a, const IDType& b) const { const nod::Node* node = m_node.get(); const PAKType* pak = m_pak.get(); if (!pak) LogDNACommon.report(logvisor::Fatal, FMT_STRING("PAKRouter::enterPAKBridge() must be called before PAKRouter::getResourceRelativePath()")); const typename BRIDGETYPE::PAKType::Entry* be = lookupEntry(b); if (!be) return hecl::SystemString(); hecl::ProjectPath aPath = getWorking(&a, BRIDGETYPE::LookupExtractor(*node, *pak, a)); hecl::SystemString ret; for (size_t i = 0; i < aPath.levelCount(); ++i) ret += _SYS_STR("../"); hecl::ProjectPath bPath = getWorking(be, BRIDGETYPE::LookupExtractor(*node, *pak, *be)); ret += bPath.getRelativePath(); return ret; } template std::string PAKRouter::getBestEntryName(const EntryType& entry, bool stdOverride) const { std::string name; for (const BRIDGETYPE& bridge : *m_bridges) { const typename BRIDGETYPE::PAKType& pak = bridge.getPAK(); const typename BRIDGETYPE::PAKType::Entry* e = pak.lookupEntry(entry.id); if (!e) continue; if (stdOverride && !pak.m_noShare) { if (entry.type == FOURCC('MLVL')) return fmt::format(FMT_STRING("!world_{}"), entry.id); else if (entry.type == FOURCC('MREA')) return fmt::format(FMT_STRING("!area_{}"), entry.id); else if (entry.type == FOURCC('MAPA')) return fmt::format(FMT_STRING("!map_{}"), entry.id); else if (entry.type == FOURCC('PATH')) return fmt::format(FMT_STRING("!path_{}"), entry.id); else if (entry.type == FOURCC('MAPW')) return fmt::format(FMT_STRING("!mapw_{}"), entry.id); else if (entry.type == FOURCC('SAVW')) return fmt::format(FMT_STRING("!savw_{}"), entry.id); } std::string catalogueName; name = pak.bestEntryName(bridge.getNode(), entry, catalogueName); if (!catalogueName.empty()) return name; } return name; } template std::string PAKRouter::getBestEntryName(const IDType& entry, bool stdOverride) const { std::string name; for (const BRIDGETYPE& bridge : *m_bridges) { const typename BRIDGETYPE::PAKType& pak = bridge.getPAK(); const typename BRIDGETYPE::PAKType::Entry* e = pak.lookupEntry(entry); if (!e) continue; if (stdOverride && !pak.m_noShare) { if (e->type == FOURCC('MLVL')) return fmt::format(FMT_STRING("!world_{}"), e->id); else if (e->type == FOURCC('MREA')) return fmt::format(FMT_STRING("!area_{}"), e->id); else if (e->type == FOURCC('MAPA')) return fmt::format(FMT_STRING("!map_{}"), e->id); else if (e->type == FOURCC('PATH')) return fmt::format(FMT_STRING("!path_{}"), e->id); else if (e->type == FOURCC('MAPW')) return fmt::format(FMT_STRING("!mapw_{}"), e->id); else if (e->type == FOURCC('SAVW')) return fmt::format(FMT_STRING("!savw_{}"), e->id); } std::string catalogueName; name = pak.bestEntryName(bridge.getNode(), *e, catalogueName); if (!catalogueName.empty()) return name; } return name; } template bool PAKRouter::extractResources(const BRIDGETYPE& pakBridge, bool force, hecl::blender::Token& btok, std::function progress) { enterPAKBridge(pakBridge); size_t count = 0; size_t sz = m_pak->m_entries.size(); float fsz = sz; for (unsigned w = 0; count < sz; ++w) { for (const auto& item : m_pak->m_firstEntries) { const auto* entryPtr = m_pak->lookupEntry(item); ResExtractor extractor = BRIDGETYPE::LookupExtractor(*m_node.get(), *m_pak.get(), *entryPtr); if (extractor.weight != w) continue; std::string bestName = getBestEntryName(*entryPtr, false); hecl::SystemStringConv bestNameView(bestName); float thisFac = ++count / fsz; progress(bestNameView.c_str(), thisFac); const nod::Node* node = m_node.get(); hecl::ProjectPath working = getWorking(entryPtr, extractor); working.makeDirChain(false); hecl::ResourceLock resLk(working); if (!resLk) continue; /* Extract to unmodified directory */ hecl::ProjectPath cooked = working.getCookedPath(m_dataSpec.getUnmodifiedSpec()); if (force || cooked.isNone()) { cooked.makeDirChain(false); PAKEntryReadStream s = entryPtr->beginReadStream(*node); const auto fout = hecl::FopenUnique(cooked.getAbsolutePath().data(), _SYS_STR("wb")); std::fwrite(s.data(), 1, s.length(), fout.get()); } if (extractor.func_a) /* Doesn't need PAKRouter access */ { if (force || !extractor.IsFullyExtracted(working)) { PAKEntryReadStream s = entryPtr->beginReadStream(*node); extractor.func_a(s, working); } } else if (extractor.func_b) /* Needs PAKRouter access */ { if (force || !extractor.IsFullyExtracted(working)) { PAKEntryReadStream s = entryPtr->beginReadStream(*node); extractor.func_b(m_dataSpec, s, working, *this, *entryPtr, force, btok, [&progress, thisFac](const hecl::SystemChar* update) { progress(update, thisFac); }); } } } } return true; } template const typename BRIDGETYPE::PAKType::Entry* PAKRouter::lookupEntry(const IDType& entry, const nod::Node** nodeOut, bool silenceWarnings, bool currentPAK) const { if (!entry.isValid()) return nullptr; if (!m_bridges) LogDNACommon.report(logvisor::Fatal, FMT_STRING("PAKRouter::build() must be called before PAKRouter::lookupEntry()")); const PAKType* pak = m_pak.get(); const nod::Node* node = m_node.get(); if (pak) { const EntryType* ent = pak->lookupEntry(entry); if (ent) { if (nodeOut) *nodeOut = node; return ent; } } if (currentPAK) { #ifndef NDEBUG if (!silenceWarnings) LogDNACommon.report(logvisor::Warning, FMT_STRING("unable to find PAK entry {} in current PAK"), entry); #endif return nullptr; } for (const BRIDGETYPE& bridge : *m_bridges) { const PAKType& pak = bridge.getPAK(); const EntryType* ent = pak.lookupEntry(entry); if (ent) { if (nodeOut) *nodeOut = &bridge.getNode(); return ent; } } #ifndef NDEBUG if (!silenceWarnings) LogDNACommon.report(logvisor::Warning, FMT_STRING("unable to find PAK entry {}"), entry); #endif if (nodeOut) *nodeOut = nullptr; return nullptr; } template const typename CharacterAssociations::IDType>::RigPair* PAKRouter::lookupCMDLRigPair(const IDType& id) const { auto search = m_charAssoc.m_cmdlRigs.find(id); if (search == m_charAssoc.m_cmdlRigs.end()) return nullptr; return &search->second; } template const typename CharacterAssociations::IDType>::MultimapIteratorPair PAKRouter::lookupCharacterAttachmentRigs(const IDType& id) const { return m_charAssoc.m_characterToAttachmentRigs.equal_range(id); } template const zeus::CMatrix4f* PAKRouter::lookupMAPATransform(const IDType& id) const { auto search = m_mapaTransforms.find(id); if (search == m_mapaTransforms.end()) return nullptr; return &search->second; } template hecl::ProjectPath PAKRouter::getAreaLayerWorking(const IDType& areaId, int layerIdx) const { if (!m_bridges) LogDNACommon.report(logvisor::Fatal, FMT_STRING("PAKRouter::build() must be called before PAKRouter::getAreaLayerWorking()")); auto bridgePathIt = m_bridgePaths.cbegin(); for (const BRIDGETYPE& bridge : *m_bridges) { for (const auto& level : bridge.m_levelDeps) for (const auto& area : level.second.areas) if (area.first == areaId) { hecl::ProjectPath levelPath(bridgePathIt->first, level.second.name); hecl::ProjectPath areaPath(levelPath, area.second.name); if (layerIdx < 0) return areaPath; return hecl::ProjectPath(areaPath, area.second.layers.at(layerIdx).name); } ++bridgePathIt; } return hecl::ProjectPath(); } template hecl::ProjectPath PAKRouter::getAreaLayerWorking(const IDType& areaId, int layerIdx, bool& activeOut) const { activeOut = false; if (!m_bridges) LogDNACommon.report(logvisor::Fatal, FMT_STRING("PAKRouter::build() must be called before PAKRouter::getAreaLayerWorking()")); auto bridgePathIt = m_bridgePaths.cbegin(); for (const BRIDGETYPE& bridge : *m_bridges) { for (const auto& level : bridge.m_levelDeps) for (const auto& area : level.second.areas) if (area.first == areaId) { hecl::ProjectPath levelPath(bridgePathIt->first, level.second.name); hecl::ProjectPath areaPath(levelPath, area.second.name); if (layerIdx < 0) return areaPath; const typename Level::Area::Layer& layer = area.second.layers.at(layerIdx); activeOut = layer.active; return hecl::ProjectPath(areaPath, layer.name); } ++bridgePathIt; } return hecl::ProjectPath(); } template hecl::ProjectPath PAKRouter::getAreaLayerCooked(const IDType& areaId, int layerIdx) const { if (!m_bridges) LogDNACommon.report(logvisor::Fatal, FMT_STRING("PAKRouter::build() must be called before PAKRouter::getAreaLayerCooked()")); auto bridgePathIt = m_bridgePaths.cbegin(); for (const BRIDGETYPE& bridge : *m_bridges) { for (const auto& level : bridge.m_levelDeps) for (const auto& area : level.second.areas) if (area.first == areaId) { hecl::ProjectPath levelPath(bridgePathIt->second, level.second.name); hecl::ProjectPath areaPath(levelPath, area.second.name); if (layerIdx < 0) return areaPath; return hecl::ProjectPath(areaPath, area.second.layers.at(layerIdx).name); } ++bridgePathIt; } return hecl::ProjectPath(); } template hecl::ProjectPath PAKRouter::getAreaLayerCooked(const IDType& areaId, int layerIdx, bool& activeOut) const { activeOut = false; if (!m_bridges) LogDNACommon.report(logvisor::Fatal, FMT_STRING("PAKRouter::build() must be called before PAKRouter::getAreaLayerCooked()")); auto bridgePathIt = m_bridgePaths.cbegin(); for (const BRIDGETYPE& bridge : *m_bridges) { for (const auto& level : bridge.m_levelDeps) for (const auto& area : level.second.areas) if (area.first == areaId) { hecl::ProjectPath levelPath(bridgePathIt->second, level.second.name); hecl::ProjectPath areaPath(levelPath, area.second.name); if (layerIdx < 0) return areaPath; const typename Level::Area::Layer& layer = area.second.layers.at(layerIdx); activeOut = layer.active; return hecl::ProjectPath(areaPath, layer.name); } ++bridgePathIt; } return hecl::ProjectPath(); } template void PAKRouter::enumerateResources(const std::function& func) { if (!m_bridges) LogDNACommon.report(logvisor::Fatal, FMT_STRING("PAKRouter::build() must be called before PAKRouter::enumerateResources()")); for (const auto& entryPair : m_uniqueEntries) if (!func(entryPair.second.second)) return; for (const auto& entryPair : m_sharedEntries) if (!func(entryPair.second.second)) return; } template bool PAKRouter::mreaHasDupeResources(const IDType& id) const { const PAKType* pak = m_pak.get(); if (!pak) LogDNACommon.report(logvisor::Fatal, FMT_STRING("PAKRouter::enterPAKBridge() must be called before PAKRouter::mreaHasDupeResources()")); return pak->mreaHasDupeResources(id); } template class PAKRouter; template class PAKRouter; template class PAKRouter; } // namespace DataSpec