#include "Linker.h" #include #include #include #include #include #include "Utils/ClassUtils.h" #include "Utils/Arguments/ArgumentParser.h" #include "ZoneLoading.h" #include "ObjWriting.h" #include "ObjLoading.h" #include "SearchPath/SearchPaths.h" #include "SearchPath/SearchPathFilesystem.h" #include "ObjContainer/IWD/IWD.h" #include "LinkerArgs.h" #include "ZoneWriting.h" #include "Game/IW3/ZoneCreatorIW3.h" #include "ZoneCreation/ZoneCreationContext.h" #include "ZoneCreation/IZoneCreator.h" #include "Game/IW4/ZoneCreatorIW4.h" #include "Game/IW5/ZoneCreatorIW5.h" #include "Game/T5/ZoneCreatorT5.h" #include "Game/T6/ZoneCreatorT6.h" #include "Utils/ObjFileStream.h" #include "Zone/AssetList/AssetList.h" #include "Zone/AssetList/AssetListStream.h" #include "Zone/Definition/ZoneDefinitionStream.h" namespace fs = std::filesystem; const IZoneCreator* const ZONE_CREATORS[] { new IW3::ZoneCreator(), new IW4::ZoneCreator(), new IW5::ZoneCreator(), new T5::ZoneCreator(), new T6::ZoneCreator() }; class Linker::Impl { static constexpr const char* METADATA_NAME = "name"; static constexpr const char* METADATA_GAME = "game"; static constexpr const char* METADATA_GDT = "gdt"; LinkerArgs m_args; std::vector> m_loaded_project_search_paths; SearchPaths m_asset_search_paths; SearchPaths m_gdt_search_paths; SearchPaths m_source_search_paths; std::vector> m_loaded_zones; /** * \brief Loads a search path. * \param searchPath The search path to load. */ void LoadSearchPath(ISearchPath* searchPath) const { if (m_args.m_verbose) { printf("Loading search path: \"%s\"\n", searchPath->GetPath().c_str()); } ObjLoading::LoadIWDsInSearchPath(searchPath); } /** * \brief Unloads a search path. * \param searchPath The search path to unload. */ void UnloadSearchPath(ISearchPath* searchPath) const { if (m_args.m_verbose) { printf("Unloading search path: \"%s\"\n", searchPath->GetPath().c_str()); } ObjLoading::UnloadIWDsInSearchPath(searchPath); } SearchPaths GetAssetSearchPathsForProject(const std::string& gameName, const std::string& projectName) { SearchPaths searchPathsForProject; for (const auto& searchPathStr : m_args.GetAssetSearchPathsForProject(gameName, projectName)) { auto absolutePath = fs::absolute(searchPathStr); if (!fs::is_directory(absolutePath)) { if (m_args.m_verbose) std::cout << "Adding asset search path (Not found): " << absolutePath.string() << std::endl; continue; } if (m_args.m_verbose) std::cout << "Adding asset search path: " << absolutePath.string() << std::endl; auto searchPath = std::make_unique(searchPathStr); LoadSearchPath(searchPath.get()); searchPathsForProject.IncludeSearchPath(searchPath.get()); m_loaded_project_search_paths.emplace_back(std::move(searchPath)); } searchPathsForProject.IncludeSearchPath(&m_asset_search_paths); for (auto* iwd : IWD::Repository) { searchPathsForProject.IncludeSearchPath(iwd); } return searchPathsForProject; } SearchPaths GetGdtSearchPathsForProject(const std::string& gameName, const std::string& projectName) { SearchPaths searchPathsForProject; for (const auto& searchPathStr : m_args.GetGdtSearchPathsForProject(gameName, projectName)) { auto absolutePath = fs::absolute(searchPathStr); if (!fs::is_directory(absolutePath)) { if (m_args.m_verbose) std::cout << "Adding gdt search path (Not found): " << absolutePath.string() << std::endl; continue; } if (m_args.m_verbose) std::cout << "Adding gdt search path: " << absolutePath.string() << std::endl; searchPathsForProject.CommitSearchPath(std::make_unique(searchPathStr)); } searchPathsForProject.IncludeSearchPath(&m_gdt_search_paths); return searchPathsForProject; } SearchPaths GetSourceSearchPathsForProject(const std::string& projectName) { SearchPaths searchPathsForProject; for (const auto& searchPathStr : m_args.GetSourceSearchPathsForProject(projectName)) { auto absolutePath = fs::absolute(searchPathStr); if (!fs::is_directory(absolutePath)) { if (m_args.m_verbose) std::cout << "Adding source search path (Not found): " << absolutePath.string() << std::endl; continue; } if (m_args.m_verbose) std::cout << "Adding source search path: " << absolutePath.string() << std::endl; searchPathsForProject.CommitSearchPath(std::make_unique(searchPathStr)); } searchPathsForProject.IncludeSearchPath(&m_source_search_paths); return searchPathsForProject; } /** * \brief Initializes the Linker object's search paths based on the user's input. * \return \c true if building the search paths was successful, otherwise \c false. */ bool BuildProjectIndependentSearchPaths() { for (const auto& path : m_args.GetProjectIndependentAssetSearchPaths()) { auto absolutePath = fs::absolute(path); if (!fs::is_directory(absolutePath)) { if (m_args.m_verbose) std::cout << "Adding asset search path (Not found): " << absolutePath.string() << std::endl; continue; } if (m_args.m_verbose) std::cout << "Adding asset search path: " << absolutePath.string() << std::endl; auto searchPath = std::make_unique(absolutePath.string()); LoadSearchPath(searchPath.get()); m_asset_search_paths.CommitSearchPath(std::move(searchPath)); } for (const auto& path : m_args.GetProjectIndependentGdtSearchPaths()) { auto absolutePath = fs::absolute(path); if (!fs::is_directory(absolutePath)) { if (m_args.m_verbose) std::cout << "Loading gdt search path (Not found): " << absolutePath.string() << std::endl; continue; } if (m_args.m_verbose) std::cout << "Adding gdt search path: " << absolutePath.string() << std::endl; m_gdt_search_paths.CommitSearchPath(std::make_unique(absolutePath.string())); } for (const auto& path : m_args.GetProjectIndependentSourceSearchPaths()) { auto absolutePath = fs::absolute(path); if (!fs::is_directory(absolutePath)) { if (m_args.m_verbose) std::cout << "Loading source search path (Not found): " << absolutePath.string() << std::endl; continue; } if (m_args.m_verbose) std::cout << "Adding source search path: " << absolutePath.string() << std::endl; m_source_search_paths.CommitSearchPath(std::make_unique(absolutePath.string())); } return true; } bool IncludeAdditionalZoneDefinitions(const std::string& initialFileName, ZoneDefinition& zoneDefinition, ISearchPath* sourceSearchPath) const { std::set sourceNames; sourceNames.emplace(initialFileName); std::deque toIncludeQueue; for (const auto& include : zoneDefinition.m_includes) toIncludeQueue.emplace_back(include); while (!toIncludeQueue.empty()) { const auto& source = toIncludeQueue.front(); if (sourceNames.find(source) == sourceNames.end()) { sourceNames.emplace(source); std::unique_ptr includeDefinition; { const auto definitionFileName = source + ".zone"; const auto definitionStream = sourceSearchPath->Open(definitionFileName); if (!definitionStream.IsOpen()) { std::cout << "Could not find zone definition file for project \"" << source << "\"." << std::endl; return false; } ZoneDefinitionInputStream zoneDefinitionInputStream(*definitionStream.m_stream, definitionFileName, m_args.m_verbose); includeDefinition = zoneDefinitionInputStream.ReadDefinition(); } if (!includeDefinition) { std::cout << "Failed to read zone definition file for project \"" << source << "\"." << std::endl; return false; } for (const auto& include : includeDefinition->m_includes) toIncludeQueue.emplace_back(include); zoneDefinition.Include(*includeDefinition); } toIncludeQueue.pop_front(); } return true; } static bool GetNameFromZoneDefinition(std::string& name, const std::string& projectName, const ZoneDefinition& zoneDefinition) { auto firstNameEntry = true; const auto [rangeBegin, rangeEnd] = zoneDefinition.m_metadata_lookup.equal_range(METADATA_NAME); for (auto i = rangeBegin; i != rangeEnd; ++i) { if (firstNameEntry) { name = i->second->m_value; firstNameEntry = false; } else { if (name != i->second->m_value) { std::cout << "Conflicting names in project \"" << projectName << "\": " << name << " != " << i->second << std::endl; return false; } } } if (firstNameEntry) name = projectName; return true; } std::unique_ptr ReadZoneDefinition(const std::string& projectName, ISearchPath* sourceSearchPath) const { std::unique_ptr zoneDefinition; { const auto definitionFileName = projectName + ".zone"; const auto definitionStream = sourceSearchPath->Open(definitionFileName); if (!definitionStream.IsOpen()) { std::cout << "Could not find zone definition file for project \"" << projectName << "\"." << std::endl; return nullptr; } ZoneDefinitionInputStream zoneDefinitionInputStream(*definitionStream.m_stream, definitionFileName, m_args.m_verbose); zoneDefinition = zoneDefinitionInputStream.ReadDefinition(); } if (!zoneDefinition) { std::cout << "Failed to read zone definition file for project \"" << projectName << "\"." << std::endl; return nullptr; } if (!GetNameFromZoneDefinition(zoneDefinition->m_name, projectName, *zoneDefinition)) return nullptr; if (!IncludeAdditionalZoneDefinitions(projectName, *zoneDefinition, sourceSearchPath)) return nullptr; return zoneDefinition; } bool ReadAssetList(const std::string& zoneName, std::vector& assetList, ISearchPath* sourceSearchPath) const { { const auto assetListFileName = "assetlist/" + zoneName + ".csv"; const auto assetListStream = sourceSearchPath->Open(assetListFileName); if (assetListStream.IsOpen()) { const AssetListInputStream stream(*assetListStream.m_stream); AssetListEntry entry; while (stream.NextEntry(entry)) { assetList.emplace_back(std::move(entry)); } return true; } } { const auto zoneDefinition = ReadZoneDefinition(zoneName, sourceSearchPath); if (zoneDefinition) { for (const auto& entry : zoneDefinition->m_assets) { assetList.emplace_back(entry.m_asset_type, entry.m_asset_name); } return true; } } return false; } bool ProcessZoneDefinitionIgnores(const std::string& projectName, ZoneCreationContext& context, ISearchPath* sourceSearchPath) const { if (context.m_definition->m_ignores.empty()) return true; std::map> zoneDefinitionAssetsByName; for (auto& entry : context.m_definition->m_assets) { zoneDefinitionAssetsByName.try_emplace(entry.m_asset_name, entry); } for (const auto& ignore : context.m_definition->m_ignores) { if (ignore == projectName) continue; std::vector assetList; if (!ReadAssetList(ignore, context.m_ignored_assets, sourceSearchPath)) { std::cout << "Failed to read asset listing for ignoring assets of project \"" << ignore << "\"." << std::endl; return false; } } return true; } static bool GetGameNameFromZoneDefinition(std::string& gameName, const std::string& projectName, const ZoneDefinition& zoneDefinition) { auto firstGameEntry = true; const auto [rangeBegin, rangeEnd] = zoneDefinition.m_metadata_lookup.equal_range(METADATA_GAME); for (auto i = rangeBegin; i != rangeEnd; ++i) { if (firstGameEntry) { gameName = i->second->m_value; firstGameEntry = false; } else { if (gameName != i->second->m_value) { std::cout << "Conflicting game names in project \"" << projectName << "\": " << gameName << " != " << i->second << std::endl; return false; } } } if (firstGameEntry) { std::cout << "No game name was specified for project \"" << projectName << "\"" << std::endl; return false; } return true; } static bool LoadGdtFilesFromZoneDefinition(std::vector>& gdtList, const ZoneDefinition& zoneDefinition, ISearchPath* gdtSearchPath) { const auto [rangeBegin, rangeEnd] = zoneDefinition.m_metadata_lookup.equal_range(METADATA_GDT); for (auto i = rangeBegin; i != rangeEnd; ++i) { const auto gdtFile = gdtSearchPath->Open(i->second->m_value + ".gdt"); if (!gdtFile.IsOpen()) { std::cout << "Failed to open file for gdt \"" << i->second->m_value << "\"" << std::endl; return false; } GdtReader gdtReader(*gdtFile.m_stream); auto gdt = std::make_unique(); if (!gdtReader.Read(*gdt)) { std::cout << "Failed to read gdt file \"" << i->second << "\"" << std::endl; return false; } gdtList.emplace_back(std::move(gdt)); } return true; } std::unique_ptr CreateZoneForDefinition(const std::string& projectName, ZoneDefinition& zoneDefinition, ISearchPath* assetSearchPath, ISearchPath* gdtSearchPath, ISearchPath* sourceSearchPath) const { const auto context = std::make_unique(assetSearchPath, &zoneDefinition); if (!ProcessZoneDefinitionIgnores(projectName, *context, sourceSearchPath)) return nullptr; if (!GetGameNameFromZoneDefinition(context->m_game_name, projectName, zoneDefinition)) return nullptr; if (!LoadGdtFilesFromZoneDefinition(context->m_gdt_files, zoneDefinition, gdtSearchPath)) return nullptr; for (const auto* assetLoader : ZONE_CREATORS) { if (assetLoader->SupportsGame(context->m_game_name)) return assetLoader->CreateZoneForDefinition(*context); } return nullptr; } bool WriteZoneToFile(const std::string& projectName, Zone* zone) const { const fs::path zoneFolderPath(m_args.GetOutputFolderPathForProject(projectName)); auto zoneFilePath(zoneFolderPath); zoneFilePath.append(zone->m_name + ".ff"); fs::create_directories(zoneFolderPath); std::ofstream stream(zoneFilePath, std::fstream::out | std::fstream::binary); if (!stream.is_open()) return false; if (!ZoneWriting::WriteZone(stream, zone)) { std::cout << "Writing zone failed." << std::endl; stream.close(); return false; } std::cout << "Created zone \"" << zoneFilePath.string() << "\"\n"; stream.close(); return true; } bool BuildProject(const std::string& projectName) { auto sourceSearchPaths = GetSourceSearchPathsForProject(projectName); const auto zoneDefinition = ReadZoneDefinition(projectName, &sourceSearchPaths); if (!zoneDefinition) return false; std::string gameName; if (!GetGameNameFromZoneDefinition(gameName, projectName, *zoneDefinition)) return false; for (auto& c : gameName) c = static_cast(std::tolower(c)); auto assetSearchPaths = GetAssetSearchPathsForProject(gameName, projectName); auto gdtSearchPaths = GetGdtSearchPathsForProject(gameName, projectName); const auto zone = CreateZoneForDefinition(projectName, *zoneDefinition, &assetSearchPaths, &gdtSearchPaths, &sourceSearchPaths); auto result = zone != nullptr; if (zone) result = WriteZoneToFile(projectName, zone.get()); for (const auto& loadedSearchPath : m_loaded_project_search_paths) { UnloadSearchPath(loadedSearchPath.get()); } m_loaded_project_search_paths.clear(); return result; } bool LoadZones() { for (const auto& zonePath : m_args.m_zones_to_load) { if (!fs::is_regular_file(zonePath)) { std::cout << "Could not find zone file to load \"" << zonePath << "\".\n"; return false; } auto absoluteZoneDirectory = absolute(std::filesystem::path(zonePath).remove_filename()).string(); auto zone = std::unique_ptr(ZoneLoading::LoadZone(zonePath)); if (zone == nullptr) { std::cout << "Failed to load zone \"" << zonePath << "\".\n"; return false; } if (m_args.m_verbose) { std::cout << "Load zone \"" << zone->m_name << "\"\n"; } m_loaded_zones.emplace_back(std::move(zone)); } return true; } void UnloadZones() { for (auto i = m_loaded_zones.rbegin(); i != m_loaded_zones.rend(); ++i) { auto& loadedZone = *i; std::string zoneName = loadedZone->m_name; loadedZone.reset(); if (m_args.m_verbose) std::cout << "Unloaded zone \"" << zoneName << "\"\n"; } m_loaded_zones.clear(); } public: Impl() = default; /** * \copydoc Linker::Start */ bool Start(const int argc, const char** argv) { if (!m_args.ParseArgs(argc, argv)) return false; if (!BuildProjectIndependentSearchPaths()) return false; if (!LoadZones()) return false; auto result = true; for (const auto& projectName : m_args.m_projects_to_build) { if (!BuildProject(projectName)) { result = false; break; } } UnloadZones(); return result; } }; Linker::Linker() { m_impl = new Impl(); } Linker::~Linker() { delete m_impl; m_impl = nullptr; } bool Linker::Start(const int argc, const char** argv) const { return m_impl->Start(argc, argv); }