From a1693b2eb85ba42fa1a81ef19cc3a61165bb013b Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Tue, 23 Dec 2025 12:49:22 +0100 Subject: [PATCH] chore: restructure Linker and Unlinker for system testing --- premake5.lua | 12 +- src/Linker.lua | 56 -- src/Linker/Linker.cpp | 480 ------------------ src/Linker/main.cpp | 8 - src/LinkerCli.lua | 49 ++ src/LinkerCli/main.cpp | 19 + src/Linking.lua | 61 +++ src/Linking/Linker.cpp | 476 +++++++++++++++++ src/{Linker => Linking}/Linker.h | 13 +- src/{Linker => Linking}/LinkerArgs.cpp | 0 src/{Linker => Linking}/LinkerArgs.h | 0 src/{Linker => Linking}/LinkerPaths.cpp | 0 src/{Linker => Linking}/LinkerPaths.h | 0 .../ZoneCreation/ZoneCreationContext.cpp | 0 .../ZoneCreation/ZoneCreationContext.h | 0 .../ZoneCreation/ZoneCreator.cpp | 0 .../ZoneCreation/ZoneCreator.h | 0 src/Unlinker.lua | 52 -- src/Unlinker/Unlinker.cpp | 342 ------------- src/Unlinker/Unlinker.h | 24 - src/Unlinker/main.cpp | 8 - src/UnlinkerCli.lua | 49 ++ src/UnlinkerCli/main.cpp | 19 + src/Unlinking.lua | 57 +++ .../ContentLister/ContentPrinter.cpp | 0 .../ContentLister/ContentPrinter.h | 0 src/Unlinking/Unlinker.cpp | 327 ++++++++++++ src/Unlinking/Unlinker.h | 19 + src/{Unlinker => Unlinking}/UnlinkerArgs.cpp | 0 src/{Unlinker => Unlinking}/UnlinkerArgs.h | 0 src/{Unlinker => Unlinking}/UnlinkerPaths.cpp | 0 src/{Unlinker => Unlinking}/UnlinkerPaths.h | 0 32 files changed, 1089 insertions(+), 982 deletions(-) delete mode 100644 src/Linker.lua delete mode 100644 src/Linker/Linker.cpp delete mode 100644 src/Linker/main.cpp create mode 100644 src/LinkerCli.lua create mode 100644 src/LinkerCli/main.cpp create mode 100644 src/Linking.lua create mode 100644 src/Linking/Linker.cpp rename src/{Linker => Linking}/Linker.h (53%) rename src/{Linker => Linking}/LinkerArgs.cpp (100%) rename src/{Linker => Linking}/LinkerArgs.h (100%) rename src/{Linker => Linking}/LinkerPaths.cpp (100%) rename src/{Linker => Linking}/LinkerPaths.h (100%) rename src/{Linker => Linking}/ZoneCreation/ZoneCreationContext.cpp (100%) rename src/{Linker => Linking}/ZoneCreation/ZoneCreationContext.h (100%) rename src/{Linker => Linking}/ZoneCreation/ZoneCreator.cpp (100%) rename src/{Linker => Linking}/ZoneCreation/ZoneCreator.h (100%) delete mode 100644 src/Unlinker.lua delete mode 100644 src/Unlinker/Unlinker.cpp delete mode 100644 src/Unlinker/Unlinker.h delete mode 100644 src/Unlinker/main.cpp create mode 100644 src/UnlinkerCli.lua create mode 100644 src/UnlinkerCli/main.cpp create mode 100644 src/Unlinking.lua rename src/{Unlinker => Unlinking}/ContentLister/ContentPrinter.cpp (100%) rename src/{Unlinker => Unlinking}/ContentLister/ContentPrinter.h (100%) create mode 100644 src/Unlinking/Unlinker.cpp create mode 100644 src/Unlinking/Unlinker.h rename src/{Unlinker => Unlinking}/UnlinkerArgs.cpp (100%) rename src/{Unlinker => Unlinking}/UnlinkerArgs.h (100%) rename src/{Unlinker => Unlinking}/UnlinkerPaths.cpp (100%) rename src/{Unlinker => Unlinking}/UnlinkerPaths.h (100%) diff --git a/premake5.lua b/premake5.lua index be41f637..f03b5c3a 100644 --- a/premake5.lua +++ b/premake5.lua @@ -129,11 +129,13 @@ group "" include "src/Common.lua" include "src/Cryptography.lua" include "src/ImageConverter.lua" -include "src/Linker.lua" +include "src/LinkerCli.lua" +include "src/Linking.lua" include "src/ModMan.lua" include "src/Parser.lua" include "src/RawTemplater.lua" -include "src/Unlinker.lua" +include "src/UnlinkerCli.lua" +include "src/Unlinking.lua" include "src/Utils.lua" include "src/ZoneCode.lua" include "src/ZoneCodeGeneratorLib.lua" @@ -165,6 +167,8 @@ group "Components" ObjImage:project() ObjLoading:project() ObjWriting:project() + Linking:project() + Unlinking:project() group "" -- Tools group: All projects that compile into the final tools @@ -175,8 +179,8 @@ group "" -- Tools group: All projects that compile into the final tools group "Tools" - Linker:project() - Unlinker:project() + LinkerCli:project() + UnlinkerCli:project() ImageConverter:project() if _OPTIONS["modman"] then diff --git a/src/Linker.lua b/src/Linker.lua deleted file mode 100644 index a4a736d7..00000000 --- a/src/Linker.lua +++ /dev/null @@ -1,56 +0,0 @@ -Linker = {} - -function Linker:include(includes) - if includes:handle(self:name()) then - includedirs { - path.join(ProjectFolder(), "Linker") - } - end -end - -function Linker:link(links) - -end - -function Linker:use() - dependson(self:name()) -end - -function Linker:name() - return "Linker" -end - -function Linker:project() - local folder = ProjectFolder() - local includes = Includes:create() - local links = Links:create() - - project(self:name()) - targetdir(TargetDirectoryBin) - location "%{wks.location}/src/%{prj.name}" - kind "ConsoleApp" - language "C++" - - files { - path.join(folder, "Linker/**.h"), - path.join(folder, "Linker/**.cpp") - } - - self:include(includes) - Utils:include(includes) - ZoneLoading:include(includes) - ObjCompiling:include(includes) - ObjLoading:include(includes) - ObjWriting:include(includes) - ZoneWriting:include(includes) - - Raw:use() - - links:linkto(Utils) - links:linkto(ObjCompiling) - links:linkto(ZoneLoading) - links:linkto(ZoneWriting) - links:linkto(ObjLoading) - links:linkto(ObjWriting) - links:linkall() -end diff --git a/src/Linker/Linker.cpp b/src/Linker/Linker.cpp deleted file mode 100644 index e62109a5..00000000 --- a/src/Linker/Linker.cpp +++ /dev/null @@ -1,480 +0,0 @@ -#include "Linker.h" - -#include "LinkerArgs.h" -#include "LinkerPaths.h" -#include "ObjContainer/SoundBank/SoundBankWriter.h" -#include "ObjWriting.h" -#include "SearchPath/OutputPathFilesystem.h" -#include "SearchPath/SearchPaths.h" -#include "Utils/Logging/Log.h" -#include "Utils/ObjFileStream.h" -#include "Zone/AssetList/AssetList.h" -#include "Zone/AssetList/AssetListReader.h" -#include "Zone/Definition/ZoneDefinitionStream.h" -#include "ZoneCreation/ZoneCreationContext.h" -#include "ZoneCreation/ZoneCreator.h" -#include "ZoneLoading.h" -#include "ZoneWriting.h" - -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -namespace -{ - class LinkerSearchPathContext - { - public: - explicit LinkerSearchPathContext(const ILinkerSearchPathBuilder& searchPathBuilder) - : m_search_path_builder(searchPathBuilder) - { - m_independent_search_paths = m_search_path_builder.BuildIndependentSearchPaths(); - if (m_independent_search_paths) - m_search_paths.IncludeSearchPath(m_independent_search_paths.get()); - } - - [[nodiscard]] ISearchPath& GetSearchPaths() - { - return m_search_paths; - } - - void LoadProjectSpecific(const std::string& projectName) - { - m_project_specific_search_paths = m_search_path_builder.BuildSearchPathsSpecificToProject(projectName); - if (m_project_specific_search_paths) - m_search_paths.IncludeSearchPath(m_project_specific_search_paths.get()); - } - - void UnloadProjectSpecific() - { - if (!m_project_specific_search_paths) - return; - - m_search_paths.RemoveSearchPath(m_project_specific_search_paths.get()); - m_project_specific_search_paths.reset(); - } - - void LoadGameSpecific(const std::string& projectName, const GameId game) - { - m_game_specific_search_paths = m_search_path_builder.BuildSearchPathsSpecificToProjectAndGame(projectName, game); - if (m_game_specific_search_paths) - m_search_paths.IncludeSearchPath(m_game_specific_search_paths.get()); - } - - void UnloadGameSpecific() - { - if (!m_game_specific_search_paths) - return; - - m_search_paths.RemoveSearchPath(m_game_specific_search_paths.get()); - m_game_specific_search_paths.reset(); - } - - private: - const ILinkerSearchPathBuilder& m_search_path_builder; - std::unique_ptr m_independent_search_paths; - std::unique_ptr m_project_specific_search_paths; - std::unique_ptr m_game_specific_search_paths; - SearchPaths m_search_paths; - }; - - class LinkerPathManager - { - public: - explicit LinkerPathManager(const LinkerArgs& args) - : m_linker_paths(ILinkerPaths::FromArgs(args)), - m_asset_paths(m_linker_paths->AssetSearchPaths()), - m_gdt_paths(m_linker_paths->GdtSearchPaths()), - m_source_paths(m_linker_paths->SourceSearchPaths()) - { - } - - std::unique_ptr m_linker_paths; - LinkerSearchPathContext m_asset_paths; - LinkerSearchPathContext m_gdt_paths; - LinkerSearchPathContext m_source_paths; - }; - - class PathProjectContext - { - public: - PathProjectContext(LinkerPathManager& paths, const std::string& projectName) - : m_paths(paths) - { - m_paths.m_asset_paths.LoadProjectSpecific(projectName); - m_paths.m_gdt_paths.LoadProjectSpecific(projectName); - m_paths.m_source_paths.LoadProjectSpecific(projectName); - } - - ~PathProjectContext() - { - m_paths.m_asset_paths.UnloadProjectSpecific(); - m_paths.m_gdt_paths.UnloadProjectSpecific(); - m_paths.m_source_paths.UnloadProjectSpecific(); - } - - PathProjectContext(const PathProjectContext& other) = delete; - PathProjectContext(PathProjectContext&& other) noexcept = delete; - PathProjectContext& operator=(const PathProjectContext& other) = delete; - PathProjectContext& operator=(PathProjectContext&& other) noexcept = delete; - - private: - LinkerPathManager& m_paths; - }; - - class PathGameContext - { - public: - PathGameContext(LinkerPathManager& paths, const std::string& projectName, const GameId game) - : m_paths(paths) - { - m_paths.m_asset_paths.LoadGameSpecific(projectName, game); - m_paths.m_gdt_paths.LoadGameSpecific(projectName, game); - m_paths.m_source_paths.LoadGameSpecific(projectName, game); - } - - ~PathGameContext() - { - m_paths.m_asset_paths.UnloadGameSpecific(); - m_paths.m_gdt_paths.UnloadGameSpecific(); - m_paths.m_source_paths.UnloadGameSpecific(); - } - - PathGameContext(const PathGameContext& other) = delete; - PathGameContext(PathGameContext&& other) noexcept = delete; - PathGameContext& operator=(const PathGameContext& other) = delete; - PathGameContext& operator=(PathGameContext&& other) noexcept = delete; - - private: - LinkerPathManager& m_paths; - }; -} // namespace - -class LinkerImpl final : public Linker -{ - std::unique_ptr ReadZoneDefinition(LinkerPathManager& paths, const std::string& targetName, bool logMissing = true) const - { - auto& sourceSearchPath = paths.m_source_paths.GetSearchPaths(); - std::unique_ptr zoneDefinition; - { - const auto definitionFileName = std::format("{}.zone", targetName); - const auto definitionStream = sourceSearchPath.Open(definitionFileName); - if (!definitionStream.IsOpen()) - { - if (logMissing) - con::error("Could not find zone definition file for target \"{}\".", targetName); - return nullptr; - } - - ZoneDefinitionInputStream zoneDefinitionInputStream(*definitionStream.m_stream, targetName, definitionFileName, sourceSearchPath); - zoneDefinition = zoneDefinitionInputStream.ReadDefinition(); - } - - if (!zoneDefinition) - { - con::error("Failed to read zone definition file for target \"{}\".", targetName); - return nullptr; - } - - return zoneDefinition; - } - - bool ReadIgnoreEntries(LinkerPathManager& paths, const std::string& zoneName, const GameId game, AssetList& assetList) const - { - { - AssetListReader assetListReader(paths.m_source_paths.GetSearchPaths(), game); - const auto maybeReadAssetList = assetListReader.ReadAssetList(zoneName, false); - if (maybeReadAssetList) - { - assetList.m_entries.reserve(assetList.m_entries.size() + maybeReadAssetList->m_entries.size()); - for (auto& entry : maybeReadAssetList->m_entries) - assetList.m_entries.emplace_back(std::move(entry)); - - return true; - } - } - - { - const auto zoneDefinition = ReadZoneDefinition(paths, zoneName, false); - - if (zoneDefinition) - { - assetList.m_entries.reserve(assetList.m_entries.size() + zoneDefinition->m_assets.size()); - for (const auto& entry : zoneDefinition->m_assets) - assetList.m_entries.emplace_back(entry.m_asset_type, entry.m_asset_name, entry.m_is_reference); - - return true; - } - } - - return false; - } - - bool ProcessZoneDefinitionIgnores(LinkerPathManager& paths, const std::string& targetName, ZoneCreationContext& context) const - { - if (context.m_definition->m_ignores.empty()) - return true; - - for (const auto& ignore : context.m_definition->m_ignores) - { - if (ignore == targetName) - continue; - - if (!ReadIgnoreEntries(paths, ignore, context.m_definition->m_game, context.m_ignored_assets)) - { - con::error("Failed to read asset listing for ignoring assets of project \"{}\".", ignore); - return false; - } - } - return true; - } - - static bool LoadGdtFilesFromZoneDefinition(std::vector>& gdtList, const ZoneDefinition& zoneDefinition, ISearchPath* gdtSearchPath) - { - for (const auto& gdtName : zoneDefinition.m_gdts) - { - const auto gdtFile = gdtSearchPath->Open(std::format("{}.gdt", gdtName)); - if (!gdtFile.IsOpen()) - { - con::error("Failed to open file for gdt \"{}\"", gdtName); - return false; - } - - GdtReader gdtReader(*gdtFile.m_stream); - auto gdt = std::make_unique(); - if (!gdtReader.Read(*gdt)) - { - con::error("Failed to read gdt file \"{}\"", gdtName); - return false; - } - - gdtList.emplace_back(std::move(gdt)); - } - - return true; - } - - std::unique_ptr CreateZoneForDefinition( - LinkerPathManager& paths, const fs::path& outDir, const fs::path& cacheDir, const std::string& targetName, ZoneDefinition& zoneDefinition) const - { - ZoneCreationContext context(&zoneDefinition, &paths.m_asset_paths.GetSearchPaths(), outDir, cacheDir); - if (!ProcessZoneDefinitionIgnores(paths, targetName, context)) - return nullptr; - if (!LoadGdtFilesFromZoneDefinition(context.m_gdt_files, zoneDefinition, &paths.m_gdt_paths.GetSearchPaths())) - return nullptr; - - return zone_creator::CreateZoneForDefinition(zoneDefinition.m_game, context); - } - - static bool WriteZoneToFile(IOutputPath& outPath, const Zone& zone) - { - const auto stream = outPath.Open(std::format("{}.ff", zone.m_name)); - if (!stream) - { - con::error("Failed to open file for zone: {}", zone.m_name); - return false; - } - - con::info("Building zone \"{}\"", zone.m_name); - - if (!ZoneWriting::WriteZone(*stream, zone)) - { - con::error("Writing zone failed."); - return false; - } - - con::info("Created zone \"{}\"", zone.m_name); - - return true; - } - - bool BuildFastFile(LinkerPathManager& paths, const std::string& projectName, const std::string& targetName, ZoneDefinition& zoneDefinition) const - { - const fs::path outDir(paths.m_linker_paths->BuildOutputFolderPath(projectName, zoneDefinition.m_game)); - - OutputPathFilesystem outputPath(outDir); - - const fs::path cacheDir(paths.m_linker_paths->BuildCacheFolderPath(projectName, zoneDefinition.m_game)); - SoundBankWriter::OutputPath = outDir; - - const auto zone = CreateZoneForDefinition(paths, outDir, cacheDir, targetName, zoneDefinition); - auto result = zone != nullptr; - if (zone) - result = WriteZoneToFile(outputPath, *zone); - - return result; - } - - bool BuildProject(LinkerPathManager& paths, const std::string& projectName, const std::string& targetName) const - { - std::deque targetsToBuild; - std::unordered_set alreadyBuiltTargets; - - targetsToBuild.emplace_back(targetName); - - while (!targetsToBuild.empty()) - { - const auto currentTarget = std::move(targetsToBuild.front()); - targetsToBuild.pop_front(); - alreadyBuiltTargets.emplace(currentTarget); - - PathProjectContext projectContext(paths, projectName); - - const auto zoneDefinition = ReadZoneDefinition(paths, targetName); - if (!zoneDefinition) - return false; - - PathGameContext gameContext(paths, projectName, zoneDefinition->m_game); - - if (!zoneDefinition->m_assets.empty()) - { - if (!BuildFastFile(paths, projectName, targetName, *zoneDefinition)) - return false; - - for (const auto& referencedTarget : zoneDefinition->m_targets_to_build) - { - if (alreadyBuiltTargets.find(referencedTarget) == alreadyBuiltTargets.end()) - { - targetsToBuild.emplace_back(referencedTarget); - con::info("Building referenced target \"{}\"", referencedTarget); - } - } - } - } - - return true; - } - - bool LoadZones() - { - for (const auto& zonePath : m_args.m_zones_to_load) - { - if (!fs::is_regular_file(zonePath)) - { - con::error("Could not find zone file to load \"{}\".", zonePath); - return false; - } - - auto zoneDirectory = fs::path(zonePath).remove_filename(); - if (zoneDirectory.empty()) - zoneDirectory = fs::current_path(); - auto absoluteZoneDirectory = absolute(zoneDirectory).string(); - - auto maybeZone = ZoneLoading::LoadZone(zonePath, std::nullopt); - if (!maybeZone) - { - con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error()); - return false; - } - - auto zone = std::move(*maybeZone); - - con::debug("Loaded zone \"{}\"", zone->m_name); - - 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(); - - con::debug("Unloaded zone \"{}\"", zoneName); - } - m_loaded_zones.clear(); - } - - static bool GetProjectAndTargetFromProjectSpecifier(const std::string& projectSpecifier, std::string& projectName, std::string& targetName) - { - const auto targetNameSeparatorIndex = projectSpecifier.find_first_of('/'); - if (targetNameSeparatorIndex == std::string::npos) - { - projectName = projectSpecifier; - targetName = projectSpecifier; - } - else if (projectSpecifier.find_first_of('/', targetNameSeparatorIndex + 1) != std::string::npos) - { - con::error("Project specifier cannot have more than one target name: \"{}\"", projectSpecifier); - return false; - } - else - { - projectName = projectSpecifier.substr(0, targetNameSeparatorIndex); - targetName = projectSpecifier.substr(targetNameSeparatorIndex + 1); - } - - if (projectName.empty()) - { - con::error("Project name cannot be empty: \"{}\"", projectSpecifier); - return false; - } - - if (targetName.empty()) - { - con::error("Target name cannot be empty: \"{}\"", projectSpecifier); - return false; - } - - return true; - } - -public: - bool Start(const int argc, const char** argv) override - { - con::init(); - - auto shouldContinue = true; - if (!m_args.ParseArgs(argc, argv, shouldContinue)) - return false; - - if (!shouldContinue) - return true; - - LinkerPathManager paths(m_args); - - if (!LoadZones()) - return false; - - auto result = true; - for (const auto& projectSpecifier : m_args.m_project_specifiers_to_build) - { - std::string projectName; - std::string targetName; - if (!GetProjectAndTargetFromProjectSpecifier(projectSpecifier, projectName, targetName)) - { - result = false; - break; - } - - if (!BuildProject(paths, projectName, targetName)) - { - result = false; - break; - } - } - - UnloadZones(); - - return result; - } - -private: - LinkerArgs m_args; - std::vector> m_loaded_zones; -}; - -std::unique_ptr Linker::Create() -{ - return std::make_unique(); -} diff --git a/src/Linker/main.cpp b/src/Linker/main.cpp deleted file mode 100644 index 36f84947..00000000 --- a/src/Linker/main.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "Linker.h" - -int main(const int argc, const char** argv) -{ - const auto linker = Linker::Create(); - - return linker->Start(argc, argv) ? 0 : 1; -} diff --git a/src/LinkerCli.lua b/src/LinkerCli.lua new file mode 100644 index 00000000..2c93816e --- /dev/null +++ b/src/LinkerCli.lua @@ -0,0 +1,49 @@ +LinkerCli = {} + +function LinkerCli:include(includes) + if includes:handle(self:name()) then + includedirs { + path.join(ProjectFolder(), "LinkerCli") + } + end +end + +function LinkerCli:link(links) + +end + +function LinkerCli:use() + dependson(self:name()) +end + +function LinkerCli:name() + return "LinkerCli" +end + +function LinkerCli:project() + local folder = ProjectFolder() + local includes = Includes:create() + local links = Links:create() + + project(self:name()) + targetdir(TargetDirectoryBin) + targetname "Linker" + location "%{wks.location}/src/%{prj.name}" + kind "ConsoleApp" + language "C++" + + files { + path.join(folder, "LinkerCli/**.h"), + path.join(folder, "LinkerCli/**.cpp") + } + + self:include(includes) + Utils:include(includes) + Linking:include(includes) + + Raw:use() + + links:linkto(Utils) + links:linkto(Linking) + links:linkall() +end diff --git a/src/LinkerCli/main.cpp b/src/LinkerCli/main.cpp new file mode 100644 index 00000000..0eec80a3 --- /dev/null +++ b/src/LinkerCli/main.cpp @@ -0,0 +1,19 @@ +#include "Linker.h" +#include "Utils/Logging/Log.h" + +int main(const int argc, const char** argv) +{ + con::init(); + + LinkerArgs args; + auto shouldContinue = true; + if (!args.ParseArgs(argc, argv, shouldContinue)) + return 1; + + if (!shouldContinue) + return 0; + + const auto linker = Linker::Create(std::move(args)); + + return linker->Start() ? 0 : 1; +} diff --git a/src/Linking.lua b/src/Linking.lua new file mode 100644 index 00000000..e969ebee --- /dev/null +++ b/src/Linking.lua @@ -0,0 +1,61 @@ +Linking = {} + +function Linking:include(includes) + if includes:handle(self:name()) then + ZoneCommon:include(includes) + includedirs { + path.join(ProjectFolder(), "Linking") + } + end +end + +function Linking:link(links) + links:add(self:name()) + links:linkto(Utils) + links:linkto(ObjCompiling) + links:linkto(ZoneLoading) + links:linkto(ZoneWriting) + links:linkto(ObjLoading) + links:linkto(ObjWriting) +end + +function Linking:use() + +end + +function Linking:name() + return "Linking" +end + +function Linking:project() + local folder = ProjectFolder() + local includes = Includes:create() + + project(self:name()) + targetdir(TargetDirectoryLib) + location "%{wks.location}/src/%{prj.name}" + kind "StaticLib" + language "C++" + + files { + path.join(folder, "Linking/**.h"), + path.join(folder, "Linking/**.cpp") + } + + vpaths { + ["*"] = { + path.join(folder, "Linking") + } + } + + ObjCommon:use() + useSourceTemplating("Linking") + + self:include(includes) + Utils:include(includes) + ZoneLoading:include(includes) + ObjCompiling:include(includes) + ObjLoading:include(includes) + ObjWriting:include(includes) + ZoneWriting:include(includes) +end diff --git a/src/Linking/Linker.cpp b/src/Linking/Linker.cpp new file mode 100644 index 00000000..e2b3e874 --- /dev/null +++ b/src/Linking/Linker.cpp @@ -0,0 +1,476 @@ +#include "Linker.h" + +#include "LinkerArgs.h" +#include "LinkerPaths.h" +#include "ObjContainer/SoundBank/SoundBankWriter.h" +#include "ObjWriting.h" +#include "SearchPath/OutputPathFilesystem.h" +#include "SearchPath/SearchPaths.h" +#include "Utils/Logging/Log.h" +#include "Utils/ObjFileStream.h" +#include "Zone/AssetList/AssetList.h" +#include "Zone/AssetList/AssetListReader.h" +#include "Zone/Definition/ZoneDefinitionStream.h" +#include "ZoneCreation/ZoneCreationContext.h" +#include "ZoneCreation/ZoneCreator.h" +#include "ZoneLoading.h" +#include "ZoneWriting.h" + +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace +{ + class LinkerSearchPathContext + { + public: + explicit LinkerSearchPathContext(const ILinkerSearchPathBuilder& searchPathBuilder) + : m_search_path_builder(searchPathBuilder) + { + m_independent_search_paths = m_search_path_builder.BuildIndependentSearchPaths(); + if (m_independent_search_paths) + m_search_paths.IncludeSearchPath(m_independent_search_paths.get()); + } + + [[nodiscard]] ISearchPath& GetSearchPaths() + { + return m_search_paths; + } + + void LoadProjectSpecific(const std::string& projectName) + { + m_project_specific_search_paths = m_search_path_builder.BuildSearchPathsSpecificToProject(projectName); + if (m_project_specific_search_paths) + m_search_paths.IncludeSearchPath(m_project_specific_search_paths.get()); + } + + void UnloadProjectSpecific() + { + if (!m_project_specific_search_paths) + return; + + m_search_paths.RemoveSearchPath(m_project_specific_search_paths.get()); + m_project_specific_search_paths.reset(); + } + + void LoadGameSpecific(const std::string& projectName, const GameId game) + { + m_game_specific_search_paths = m_search_path_builder.BuildSearchPathsSpecificToProjectAndGame(projectName, game); + if (m_game_specific_search_paths) + m_search_paths.IncludeSearchPath(m_game_specific_search_paths.get()); + } + + void UnloadGameSpecific() + { + if (!m_game_specific_search_paths) + return; + + m_search_paths.RemoveSearchPath(m_game_specific_search_paths.get()); + m_game_specific_search_paths.reset(); + } + + private: + const ILinkerSearchPathBuilder& m_search_path_builder; + std::unique_ptr m_independent_search_paths; + std::unique_ptr m_project_specific_search_paths; + std::unique_ptr m_game_specific_search_paths; + SearchPaths m_search_paths; + }; + + class LinkerPathManager + { + public: + explicit LinkerPathManager(const LinkerArgs& args) + : m_linker_paths(ILinkerPaths::FromArgs(args)), + m_asset_paths(m_linker_paths->AssetSearchPaths()), + m_gdt_paths(m_linker_paths->GdtSearchPaths()), + m_source_paths(m_linker_paths->SourceSearchPaths()) + { + } + + std::unique_ptr m_linker_paths; + LinkerSearchPathContext m_asset_paths; + LinkerSearchPathContext m_gdt_paths; + LinkerSearchPathContext m_source_paths; + }; + + class PathProjectContext + { + public: + PathProjectContext(LinkerPathManager& paths, const std::string& projectName) + : m_paths(paths) + { + m_paths.m_asset_paths.LoadProjectSpecific(projectName); + m_paths.m_gdt_paths.LoadProjectSpecific(projectName); + m_paths.m_source_paths.LoadProjectSpecific(projectName); + } + + ~PathProjectContext() + { + m_paths.m_asset_paths.UnloadProjectSpecific(); + m_paths.m_gdt_paths.UnloadProjectSpecific(); + m_paths.m_source_paths.UnloadProjectSpecific(); + } + + PathProjectContext(const PathProjectContext& other) = delete; + PathProjectContext(PathProjectContext&& other) noexcept = delete; + PathProjectContext& operator=(const PathProjectContext& other) = delete; + PathProjectContext& operator=(PathProjectContext&& other) noexcept = delete; + + private: + LinkerPathManager& m_paths; + }; + + class PathGameContext + { + public: + PathGameContext(LinkerPathManager& paths, const std::string& projectName, const GameId game) + : m_paths(paths) + { + m_paths.m_asset_paths.LoadGameSpecific(projectName, game); + m_paths.m_gdt_paths.LoadGameSpecific(projectName, game); + m_paths.m_source_paths.LoadGameSpecific(projectName, game); + } + + ~PathGameContext() + { + m_paths.m_asset_paths.UnloadGameSpecific(); + m_paths.m_gdt_paths.UnloadGameSpecific(); + m_paths.m_source_paths.UnloadGameSpecific(); + } + + PathGameContext(const PathGameContext& other) = delete; + PathGameContext(PathGameContext&& other) noexcept = delete; + PathGameContext& operator=(const PathGameContext& other) = delete; + PathGameContext& operator=(PathGameContext&& other) noexcept = delete; + + private: + LinkerPathManager& m_paths; + }; + + class LinkerImpl final : public Linker + { + public: + LinkerImpl(LinkerArgs args) + : m_args(std::move(args)) + { + } + + bool Start() override + { + LinkerPathManager paths(m_args); + + if (!LoadZones()) + return false; + + auto result = true; + for (const auto& projectSpecifier : m_args.m_project_specifiers_to_build) + { + std::string projectName; + std::string targetName; + if (!GetProjectAndTargetFromProjectSpecifier(projectSpecifier, projectName, targetName)) + { + result = false; + break; + } + + if (!BuildProject(paths, projectName, targetName)) + { + result = false; + break; + } + } + + UnloadZones(); + + return result; + } + + private: + std::unique_ptr ReadZoneDefinition(LinkerPathManager& paths, const std::string& targetName, bool logMissing = true) const + { + auto& sourceSearchPath = paths.m_source_paths.GetSearchPaths(); + std::unique_ptr zoneDefinition; + { + const auto definitionFileName = std::format("{}.zone", targetName); + const auto definitionStream = sourceSearchPath.Open(definitionFileName); + if (!definitionStream.IsOpen()) + { + if (logMissing) + con::error("Could not find zone definition file for target \"{}\".", targetName); + return nullptr; + } + + ZoneDefinitionInputStream zoneDefinitionInputStream(*definitionStream.m_stream, targetName, definitionFileName, sourceSearchPath); + zoneDefinition = zoneDefinitionInputStream.ReadDefinition(); + } + + if (!zoneDefinition) + { + con::error("Failed to read zone definition file for target \"{}\".", targetName); + return nullptr; + } + + return zoneDefinition; + } + + bool ReadIgnoreEntries(LinkerPathManager& paths, const std::string& zoneName, const GameId game, AssetList& assetList) const + { + { + AssetListReader assetListReader(paths.m_source_paths.GetSearchPaths(), game); + const auto maybeReadAssetList = assetListReader.ReadAssetList(zoneName, false); + if (maybeReadAssetList) + { + assetList.m_entries.reserve(assetList.m_entries.size() + maybeReadAssetList->m_entries.size()); + for (auto& entry : maybeReadAssetList->m_entries) + assetList.m_entries.emplace_back(std::move(entry)); + + return true; + } + } + + { + const auto zoneDefinition = ReadZoneDefinition(paths, zoneName, false); + + if (zoneDefinition) + { + assetList.m_entries.reserve(assetList.m_entries.size() + zoneDefinition->m_assets.size()); + for (const auto& entry : zoneDefinition->m_assets) + assetList.m_entries.emplace_back(entry.m_asset_type, entry.m_asset_name, entry.m_is_reference); + + return true; + } + } + + return false; + } + + bool ProcessZoneDefinitionIgnores(LinkerPathManager& paths, const std::string& targetName, ZoneCreationContext& context) const + { + if (context.m_definition->m_ignores.empty()) + return true; + + for (const auto& ignore : context.m_definition->m_ignores) + { + if (ignore == targetName) + continue; + + if (!ReadIgnoreEntries(paths, ignore, context.m_definition->m_game, context.m_ignored_assets)) + { + con::error("Failed to read asset listing for ignoring assets of project \"{}\".", ignore); + return false; + } + } + return true; + } + + static bool LoadGdtFilesFromZoneDefinition(std::vector>& gdtList, const ZoneDefinition& zoneDefinition, ISearchPath* gdtSearchPath) + { + for (const auto& gdtName : zoneDefinition.m_gdts) + { + const auto gdtFile = gdtSearchPath->Open(std::format("{}.gdt", gdtName)); + if (!gdtFile.IsOpen()) + { + con::error("Failed to open file for gdt \"{}\"", gdtName); + return false; + } + + GdtReader gdtReader(*gdtFile.m_stream); + auto gdt = std::make_unique(); + if (!gdtReader.Read(*gdt)) + { + con::error("Failed to read gdt file \"{}\"", gdtName); + return false; + } + + gdtList.emplace_back(std::move(gdt)); + } + + return true; + } + + std::unique_ptr CreateZoneForDefinition( + LinkerPathManager& paths, const fs::path& outDir, const fs::path& cacheDir, const std::string& targetName, ZoneDefinition& zoneDefinition) const + { + ZoneCreationContext context(&zoneDefinition, &paths.m_asset_paths.GetSearchPaths(), outDir, cacheDir); + if (!ProcessZoneDefinitionIgnores(paths, targetName, context)) + return nullptr; + if (!LoadGdtFilesFromZoneDefinition(context.m_gdt_files, zoneDefinition, &paths.m_gdt_paths.GetSearchPaths())) + return nullptr; + + return zone_creator::CreateZoneForDefinition(zoneDefinition.m_game, context); + } + + static bool WriteZoneToFile(IOutputPath& outPath, const Zone& zone) + { + const auto stream = outPath.Open(std::format("{}.ff", zone.m_name)); + if (!stream) + { + con::error("Failed to open file for zone: {}", zone.m_name); + return false; + } + + con::info("Building zone \"{}\"", zone.m_name); + + if (!ZoneWriting::WriteZone(*stream, zone)) + { + con::error("Writing zone failed."); + return false; + } + + con::info("Created zone \"{}\"", zone.m_name); + + return true; + } + + bool BuildFastFile(LinkerPathManager& paths, const std::string& projectName, const std::string& targetName, ZoneDefinition& zoneDefinition) const + { + const fs::path outDir(paths.m_linker_paths->BuildOutputFolderPath(projectName, zoneDefinition.m_game)); + + OutputPathFilesystem outputPath(outDir); + + const fs::path cacheDir(paths.m_linker_paths->BuildCacheFolderPath(projectName, zoneDefinition.m_game)); + SoundBankWriter::OutputPath = outDir; + + const auto zone = CreateZoneForDefinition(paths, outDir, cacheDir, targetName, zoneDefinition); + auto result = zone != nullptr; + if (zone) + result = WriteZoneToFile(outputPath, *zone); + + return result; + } + + bool BuildProject(LinkerPathManager& paths, const std::string& projectName, const std::string& targetName) const + { + std::deque targetsToBuild; + std::unordered_set alreadyBuiltTargets; + + targetsToBuild.emplace_back(targetName); + + while (!targetsToBuild.empty()) + { + const auto currentTarget = std::move(targetsToBuild.front()); + targetsToBuild.pop_front(); + alreadyBuiltTargets.emplace(currentTarget); + + PathProjectContext projectContext(paths, projectName); + + const auto zoneDefinition = ReadZoneDefinition(paths, targetName); + if (!zoneDefinition) + return false; + + PathGameContext gameContext(paths, projectName, zoneDefinition->m_game); + + if (!zoneDefinition->m_assets.empty()) + { + if (!BuildFastFile(paths, projectName, targetName, *zoneDefinition)) + return false; + + for (const auto& referencedTarget : zoneDefinition->m_targets_to_build) + { + if (alreadyBuiltTargets.find(referencedTarget) == alreadyBuiltTargets.end()) + { + targetsToBuild.emplace_back(referencedTarget); + con::info("Building referenced target \"{}\"", referencedTarget); + } + } + } + } + + return true; + } + + bool LoadZones() + { + for (const auto& zonePath : m_args.m_zones_to_load) + { + if (!fs::is_regular_file(zonePath)) + { + con::error("Could not find zone file to load \"{}\".", zonePath); + return false; + } + + auto zoneDirectory = fs::path(zonePath).remove_filename(); + if (zoneDirectory.empty()) + zoneDirectory = fs::current_path(); + auto absoluteZoneDirectory = absolute(zoneDirectory).string(); + + auto maybeZone = ZoneLoading::LoadZone(zonePath, std::nullopt); + if (!maybeZone) + { + con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error()); + return false; + } + + auto zone = std::move(*maybeZone); + + con::debug("Loaded zone \"{}\"", zone->m_name); + + 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(); + + con::debug("Unloaded zone \"{}\"", zoneName); + } + m_loaded_zones.clear(); + } + + static bool GetProjectAndTargetFromProjectSpecifier(const std::string& projectSpecifier, std::string& projectName, std::string& targetName) + { + const auto targetNameSeparatorIndex = projectSpecifier.find_first_of('/'); + if (targetNameSeparatorIndex == std::string::npos) + { + projectName = projectSpecifier; + targetName = projectSpecifier; + } + else if (projectSpecifier.find_first_of('/', targetNameSeparatorIndex + 1) != std::string::npos) + { + con::error("Project specifier cannot have more than one target name: \"{}\"", projectSpecifier); + return false; + } + else + { + projectName = projectSpecifier.substr(0, targetNameSeparatorIndex); + targetName = projectSpecifier.substr(targetNameSeparatorIndex + 1); + } + + if (projectName.empty()) + { + con::error("Project name cannot be empty: \"{}\"", projectSpecifier); + return false; + } + + if (targetName.empty()) + { + con::error("Target name cannot be empty: \"{}\"", projectSpecifier); + return false; + } + + return true; + } + + LinkerArgs m_args; + std::vector> m_loaded_zones; + }; +} // namespace + +std::unique_ptr Linker::Create(LinkerArgs args) +{ + return std::make_unique(std::move(args)); +} diff --git a/src/Linker/Linker.h b/src/Linking/Linker.h similarity index 53% rename from src/Linker/Linker.h rename to src/Linking/Linker.h index 06bb0cb0..d233cd05 100644 --- a/src/Linker/Linker.h +++ b/src/Linking/Linker.h @@ -1,16 +1,15 @@ #pragma once + +#include "LinkerArgs.h" + #include class Linker { public: - Linker() = default; virtual ~Linker() = default; - Linker(const Linker& other) = delete; - Linker(Linker&& other) noexcept = delete; - Linker& operator=(const Linker& other) = delete; - Linker& operator=(Linker&& other) noexcept = delete; + static std::unique_ptr Create(LinkerArgs args); /** * \brief Starts the Linker application logic. @@ -18,7 +17,5 @@ public: * \param argv The command line arguments. * \return \c true if the application was successful or \c false if an error occurred. */ - virtual bool Start(int argc, const char** argv) = 0; - - static std::unique_ptr Create(); + virtual bool Start() = 0; }; diff --git a/src/Linker/LinkerArgs.cpp b/src/Linking/LinkerArgs.cpp similarity index 100% rename from src/Linker/LinkerArgs.cpp rename to src/Linking/LinkerArgs.cpp diff --git a/src/Linker/LinkerArgs.h b/src/Linking/LinkerArgs.h similarity index 100% rename from src/Linker/LinkerArgs.h rename to src/Linking/LinkerArgs.h diff --git a/src/Linker/LinkerPaths.cpp b/src/Linking/LinkerPaths.cpp similarity index 100% rename from src/Linker/LinkerPaths.cpp rename to src/Linking/LinkerPaths.cpp diff --git a/src/Linker/LinkerPaths.h b/src/Linking/LinkerPaths.h similarity index 100% rename from src/Linker/LinkerPaths.h rename to src/Linking/LinkerPaths.h diff --git a/src/Linker/ZoneCreation/ZoneCreationContext.cpp b/src/Linking/ZoneCreation/ZoneCreationContext.cpp similarity index 100% rename from src/Linker/ZoneCreation/ZoneCreationContext.cpp rename to src/Linking/ZoneCreation/ZoneCreationContext.cpp diff --git a/src/Linker/ZoneCreation/ZoneCreationContext.h b/src/Linking/ZoneCreation/ZoneCreationContext.h similarity index 100% rename from src/Linker/ZoneCreation/ZoneCreationContext.h rename to src/Linking/ZoneCreation/ZoneCreationContext.h diff --git a/src/Linker/ZoneCreation/ZoneCreator.cpp b/src/Linking/ZoneCreation/ZoneCreator.cpp similarity index 100% rename from src/Linker/ZoneCreation/ZoneCreator.cpp rename to src/Linking/ZoneCreation/ZoneCreator.cpp diff --git a/src/Linker/ZoneCreation/ZoneCreator.h b/src/Linking/ZoneCreation/ZoneCreator.h similarity index 100% rename from src/Linker/ZoneCreation/ZoneCreator.h rename to src/Linking/ZoneCreation/ZoneCreator.h diff --git a/src/Unlinker.lua b/src/Unlinker.lua deleted file mode 100644 index f0fdc2c4..00000000 --- a/src/Unlinker.lua +++ /dev/null @@ -1,52 +0,0 @@ -Unlinker = {} - -function Unlinker:include(includes) - if includes:handle(self:name()) then - includedirs { - path.join(ProjectFolder(), "Unlinker") - } - end -end - -function Unlinker:link(links) - -end - -function Unlinker:use() - dependson(self:name()) -end - -function Unlinker:name() - return "Unlinker" -end - -function Unlinker:project() - local folder = ProjectFolder() - local includes = Includes:create() - local links = Links:create() - - project(self:name()) - targetdir(TargetDirectoryBin) - location "%{wks.location}/src/%{prj.name}" - kind "ConsoleApp" - language "C++" - - files { - path.join(folder, "Unlinker/**.h"), - path.join(folder, "Unlinker/**.cpp") - } - - self:include(includes) - Utils:include(includes) - ZoneLoading:include(includes) - ObjLoading:include(includes) - ObjWriting:include(includes) - - Raw:use() - - links:linkto(Utils) - links:linkto(ZoneLoading) - links:linkto(ObjLoading) - links:linkto(ObjWriting) - links:linkall() -end diff --git a/src/Unlinker/Unlinker.cpp b/src/Unlinker/Unlinker.cpp deleted file mode 100644 index 54192060..00000000 --- a/src/Unlinker/Unlinker.cpp +++ /dev/null @@ -1,342 +0,0 @@ -#include "Unlinker.h" - -#include "ContentLister/ContentPrinter.h" -#include "IObjLoader.h" -#include "IObjWriter.h" -#include "ObjWriting.h" -#include "SearchPath/IWD.h" -#include "SearchPath/OutputPathFilesystem.h" -#include "UnlinkerArgs.h" -#include "UnlinkerPaths.h" -#include "Utils/ClassUtils.h" -#include "Utils/Logging/Log.h" -#include "Utils/ObjFileStream.h" -#include "Zone/Definition/ZoneDefWriter.h" -#include "ZoneLoading.h" - -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -class Unlinker::Impl -{ -public: - /** - * \copydoc Unlinker::Start - */ - bool Start(const int argc, const char** argv) - { - con::init(); - - auto shouldContinue = true; - if (!m_args.ParseArgs(argc, argv, shouldContinue)) - return false; - - if (!shouldContinue) - return true; - - UnlinkerPaths paths; - if (!paths.LoadUserPaths(m_args)) - return false; - - if (!LoadZones(paths)) - return false; - - const auto result = UnlinkZones(paths); - - UnloadZones(); - return result; - } - -private: - [[nodiscard]] bool ShouldLoadObj() const - { - return m_args.m_task != UnlinkerArgs::ProcessingTask::LIST && !m_args.m_skip_obj; - } - - [[nodiscard]] bool WriteZoneDefinitionFile(const Zone& zone, const fs::path& zoneDefinitionFileFolder) const - { - auto zoneDefinitionFilePath(zoneDefinitionFileFolder); - zoneDefinitionFilePath.append(zone.m_name); - zoneDefinitionFilePath.replace_extension(".zone"); - - std::ofstream zoneDefinitionFile(zoneDefinitionFilePath, std::fstream::out | std::fstream::binary); - if (!zoneDefinitionFile.is_open()) - { - con::error("Failed to open file for zone definition file of zone \"{}\".", zone.m_name); - return false; - } - - const auto* zoneDefWriter = IZoneDefWriter::GetZoneDefWriterForGame(zone.m_game_id); - zoneDefWriter->WriteZoneDef(zoneDefinitionFile, zone, m_args.m_use_gdt); - - zoneDefinitionFile.close(); - - return true; - } - - static bool OpenGdtFile(const Zone& zone, const fs::path& outputFolder, std::ofstream& stream) - { - auto gdtFilePath(outputFolder); - gdtFilePath.append("source_data"); - - fs::create_directories(gdtFilePath); - - gdtFilePath.append(zone.m_name); - gdtFilePath.replace_extension(".gdt"); - - stream = std::ofstream(gdtFilePath, std::fstream::out | std::fstream::binary); - if (!stream.is_open()) - { - con::error("Failed to open file for zone definition file of zone \"{}\".", zone.m_name); - return false; - } - - return true; - } - - void UpdateAssetIncludesAndExcludes(const AssetDumpingContext& context) const - { - const auto assetTypeCount = context.m_zone.m_pools->GetAssetTypeCount(); - - ObjWriting::Configuration.AssetTypesToHandleBitfield = std::vector(assetTypeCount); - - std::vector handledSpecifiedAssets(m_args.m_specified_asset_types.size()); - for (auto i = 0u; i < assetTypeCount; i++) - { - const auto assetTypeName = std::string(*context.m_zone.m_pools->GetAssetTypeName(i)); - - const auto foundSpecifiedEntry = m_args.m_specified_asset_type_map.find(assetTypeName); - if (foundSpecifiedEntry != m_args.m_specified_asset_type_map.end()) - { - ObjWriting::Configuration.AssetTypesToHandleBitfield[i] = m_args.m_asset_type_handling == UnlinkerArgs::AssetTypeHandling::INCLUDE; - assert(foundSpecifiedEntry->second < handledSpecifiedAssets.size()); - handledSpecifiedAssets[foundSpecifiedEntry->second] = true; - } - else - ObjWriting::Configuration.AssetTypesToHandleBitfield[i] = m_args.m_asset_type_handling == UnlinkerArgs::AssetTypeHandling::EXCLUDE; - } - - auto anySpecifiedValueInvalid = false; - for (auto i = 0u; i < handledSpecifiedAssets.size(); i++) - { - if (!handledSpecifiedAssets[i]) - { - con::error("Unknown asset type \"{}\"", m_args.m_specified_asset_types[i]); - anySpecifiedValueInvalid = true; - } - } - - if (anySpecifiedValueInvalid) - { - con::error("Valid asset types are:"); - - auto first = true; - std::ostringstream ss; - for (auto i = 0u; i < assetTypeCount; i++) - { - const auto assetTypeName = std::string(*context.m_zone.m_pools->GetAssetTypeName(i)); - - if (first) - first = false; - else - ss << ", "; - ss << assetTypeName; - } - con::error(ss.str()); - } - } - - /** - * \brief Performs the tasks specified by the command line arguments on the specified zone. - * \param searchPath The search path for obj data. - * \param zone The zone to handle. - * \return \c true if handling the zone was successful, otherwise \c false - */ - bool HandleZone(ISearchPath& searchPath, Zone& zone) const - { - if (m_args.m_task == UnlinkerArgs::ProcessingTask::LIST) - { - const ContentPrinter printer(zone); - printer.PrintContent(); - } - else if (m_args.m_task == UnlinkerArgs::ProcessingTask::DUMP) - { - const auto outputFolderPathStr = m_args.GetOutputFolderPathForZone(zone); - const fs::path outputFolderPath(outputFolderPathStr); - fs::create_directories(outputFolderPath); - - fs::path zoneDefinitionFileFolder(outputFolderPath); - zoneDefinitionFileFolder.append("zone_source"); - fs::create_directories(zoneDefinitionFileFolder); - - if (!WriteZoneDefinitionFile(zone, zoneDefinitionFileFolder)) - return false; - - OutputPathFilesystem outputFolderOutputPath(outputFolderPath); - AssetDumpingContext context(zone, outputFolderPathStr, outputFolderOutputPath, searchPath, std::nullopt); - - std::ofstream gdtStream; - if (m_args.m_use_gdt) - { - if (!OpenGdtFile(zone, outputFolderPath, gdtStream)) - return false; - auto gdt = std::make_unique(gdtStream); - gdt->BeginStream(); - - const auto* game = IGame::GetGameById(zone.m_game_id); - gdt->WriteVersion(GdtVersion(game->GetShortName(), 1)); - - context.m_gdt = std::move(gdt); - } - - UpdateAssetIncludesAndExcludes(context); - - const auto* objWriter = IObjWriter::GetObjWriterForGame(zone.m_game_id); - - auto result = objWriter->DumpZone(context); - - if (m_args.m_use_gdt) - { - context.m_gdt->EndStream(); - gdtStream.close(); - } - - if (!result) - { - con::error("Dumping zone failed!"); - return false; - } - } - - return true; - } - - bool LoadZones(UnlinkerPaths& paths) - { - for (const auto& zonePath : m_args.m_zones_to_load) - { - if (!fs::is_regular_file(zonePath)) - { - con::error("Could not find file \"{}\".", zonePath); - continue; - } - - auto absoluteZoneDirectory = absolute(std::filesystem::path(zonePath).remove_filename()).string(); - - auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory); - auto maybeZone = ZoneLoading::LoadZone(zonePath, std::nullopt); - if (!maybeZone) - { - con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error()); - return false; - } - - auto zone = std::move(*maybeZone); - - con::debug("Loaded zone \"{}\"", zone->m_name); - - if (ShouldLoadObj()) - { - const auto* objLoader = IObjLoader::GetObjLoaderForGame(zone->m_game_id); - objLoader->LoadReferencedContainersForZone(*searchPathsForZone, *zone); - } - - 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; - - // Copy zone name since we deallocate before logging - const auto zoneName = loadedZone->m_name; - - if (ShouldLoadObj()) - { - const auto* objLoader = IObjLoader::GetObjLoaderForGame(loadedZone->m_game_id); - objLoader->UnloadContainersOfZone(*loadedZone); - } - - loadedZone.reset(); - - con::debug("Unloaded zone \"{}\"", zoneName); - } - m_loaded_zones.clear(); - } - - bool UnlinkZones(UnlinkerPaths& paths) const - { - for (const auto& zonePath : m_args.m_zones_to_unlink) - { - if (!fs::is_regular_file(zonePath)) - { - con::error("Could not find file \"{}\".", zonePath); - continue; - } - - auto zoneDirectory = fs::path(zonePath).remove_filename(); - if (zoneDirectory.empty()) - zoneDirectory = fs::current_path(); - auto absoluteZoneDirectory = absolute(zoneDirectory).string(); - - auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory); - - auto maybeZone = ZoneLoading::LoadZone(zonePath, std::nullopt); - if (!maybeZone) - { - con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error()); - return false; - } - - auto zone = std::move(*maybeZone); - - con::debug("Loaded zone \"{}\"", zone->m_name); - - const auto* objLoader = IObjLoader::GetObjLoaderForGame(zone->m_game_id); - if (ShouldLoadObj()) - objLoader->LoadReferencedContainersForZone(*searchPathsForZone, *zone); - - if (!HandleZone(*searchPathsForZone, *zone)) - return false; - - if (ShouldLoadObj()) - objLoader->UnloadContainersOfZone(*zone); - - // Copy zone name for using it after freeing the zone - std::string zoneName = zone->m_name; - zone.reset(); - con::debug("Unloaded zone \"{}\"", zoneName); - } - - return true; - } - - UnlinkerArgs m_args; - std::vector> m_loaded_zones; -}; - -Unlinker::Unlinker() -{ - m_impl = new Impl(); -} - -Unlinker::~Unlinker() -{ - delete m_impl; - m_impl = nullptr; -} - -bool Unlinker::Start(const int argc, const char** argv) const -{ - return m_impl->Start(argc, argv); -} diff --git a/src/Unlinker/Unlinker.h b/src/Unlinker/Unlinker.h deleted file mode 100644 index c549682f..00000000 --- a/src/Unlinker/Unlinker.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -class Unlinker -{ - class Impl; - Impl* m_impl; - -public: - Unlinker(); - ~Unlinker(); - - Unlinker(const Unlinker& other) = delete; - Unlinker(Unlinker&& other) noexcept = delete; - Unlinker& operator=(const Unlinker& other) = delete; - Unlinker& operator=(Unlinker&& other) noexcept = delete; - - /** - * \brief Starts the Unlinker application logic. - * \param argc The amount of command line arguments specified. - * \param argv The command line arguments. - * \return \c true if the application was successful or \c false if an error occurred. - */ - bool Start(int argc, const char** argv) const; -}; diff --git a/src/Unlinker/main.cpp b/src/Unlinker/main.cpp deleted file mode 100644 index 9a326acb..00000000 --- a/src/Unlinker/main.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "Unlinker.h" - -int main(const int argc, const char** argv) -{ - Unlinker unlinker; - - return unlinker.Start(argc, argv) ? 0 : 1; -} diff --git a/src/UnlinkerCli.lua b/src/UnlinkerCli.lua new file mode 100644 index 00000000..e7842e88 --- /dev/null +++ b/src/UnlinkerCli.lua @@ -0,0 +1,49 @@ +UnlinkerCli = {} + +function UnlinkerCli:include(includes) + if includes:handle(self:name()) then + includedirs { + path.join(ProjectFolder(), "UnlinkerCli") + } + end +end + +function UnlinkerCli:link(links) + +end + +function UnlinkerCli:use() + dependson(self:name()) +end + +function UnlinkerCli:name() + return "UnlinkerCli" +end + +function UnlinkerCli:project() + local folder = ProjectFolder() + local includes = Includes:create() + local links = Links:create() + + project(self:name()) + targetdir(TargetDirectoryBin) + targetname "Unlinker" + location "%{wks.location}/src/%{prj.name}" + kind "ConsoleApp" + language "C++" + + files { + path.join(folder, "UnlinkerCli/**.h"), + path.join(folder, "UnlinkerCli/**.cpp") + } + + self:include(includes) + Utils:include(includes) + Unlinking:include(includes) + + Raw:use() + + links:linkto(Utils) + links:linkto(Unlinking) + links:linkall() +end diff --git a/src/UnlinkerCli/main.cpp b/src/UnlinkerCli/main.cpp new file mode 100644 index 00000000..07bdbcfb --- /dev/null +++ b/src/UnlinkerCli/main.cpp @@ -0,0 +1,19 @@ +#include "Unlinker.h" +#include "Utils/Logging/Log.h" + +int main(const int argc, const char** argv) +{ + con::init(); + + UnlinkerArgs args; + auto shouldContinue = true; + if (!args.ParseArgs(argc, argv, shouldContinue)) + return 1; + + if (!shouldContinue) + return 0; + + const auto unlinker = Unlinker::Create(std::move(args)); + + return unlinker->Start() ? 0 : 1; +} diff --git a/src/Unlinking.lua b/src/Unlinking.lua new file mode 100644 index 00000000..ad42dfe4 --- /dev/null +++ b/src/Unlinking.lua @@ -0,0 +1,57 @@ +Unlinking = {} + +function Unlinking:include(includes) + if includes:handle(self:name()) then + ZoneCommon:include(includes) + includedirs { + path.join(ProjectFolder(), "Unlinking"), + } + end +end + +function Unlinking:link(links) + links:add(self:name()) + links:linkto(Utils) + links:linkto(ZoneLoading) + links:linkto(ObjLoading) + links:linkto(ObjWriting) +end + +function Unlinking:use() + +end + +function Unlinking:name() + return "Unlinking" +end + +function Unlinking:project() + local folder = ProjectFolder() + local includes = Includes:create() + + project(self:name()) + targetdir(TargetDirectoryLib) + location "%{wks.location}/src/%{prj.name}" + kind "StaticLib" + language "C++" + + files { + path.join(folder, "Unlinking/**.h"), + path.join(folder, "Unlinking/**.cpp") + } + + vpaths { + ["*"] = { + path.join(folder, "Unlinking") + } + } + + ObjCommon:use() + useSourceTemplating("Unlinking") + + self:include(includes) + Utils:include(includes) + ZoneLoading:include(includes) + ObjLoading:include(includes) + ObjWriting:include(includes) +end diff --git a/src/Unlinker/ContentLister/ContentPrinter.cpp b/src/Unlinking/ContentLister/ContentPrinter.cpp similarity index 100% rename from src/Unlinker/ContentLister/ContentPrinter.cpp rename to src/Unlinking/ContentLister/ContentPrinter.cpp diff --git a/src/Unlinker/ContentLister/ContentPrinter.h b/src/Unlinking/ContentLister/ContentPrinter.h similarity index 100% rename from src/Unlinker/ContentLister/ContentPrinter.h rename to src/Unlinking/ContentLister/ContentPrinter.h diff --git a/src/Unlinking/Unlinker.cpp b/src/Unlinking/Unlinker.cpp new file mode 100644 index 00000000..be90a78c --- /dev/null +++ b/src/Unlinking/Unlinker.cpp @@ -0,0 +1,327 @@ +#include "Unlinker.h" + +#include "ContentLister/ContentPrinter.h" +#include "IObjLoader.h" +#include "IObjWriter.h" +#include "ObjWriting.h" +#include "SearchPath/IWD.h" +#include "SearchPath/OutputPathFilesystem.h" +#include "UnlinkerArgs.h" +#include "UnlinkerPaths.h" +#include "Utils/ClassUtils.h" +#include "Utils/Logging/Log.h" +#include "Utils/ObjFileStream.h" +#include "Zone/Definition/ZoneDefWriter.h" +#include "ZoneLoading.h" + +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace +{ + class UnlinkerImpl : public Unlinker + { + public: + UnlinkerImpl(UnlinkerArgs args) + : m_args(std::move(args)) + { + } + + bool Start() override + { + UnlinkerPaths paths; + if (!paths.LoadUserPaths(m_args)) + return false; + + if (!LoadZones(paths)) + return false; + + const auto result = UnlinkZones(paths); + + UnloadZones(); + return result; + } + + private: + [[nodiscard]] bool ShouldLoadObj() const + { + return m_args.m_task != UnlinkerArgs::ProcessingTask::LIST && !m_args.m_skip_obj; + } + + [[nodiscard]] bool WriteZoneDefinitionFile(const Zone& zone, const fs::path& zoneDefinitionFileFolder) const + { + auto zoneDefinitionFilePath(zoneDefinitionFileFolder); + zoneDefinitionFilePath.append(zone.m_name); + zoneDefinitionFilePath.replace_extension(".zone"); + + std::ofstream zoneDefinitionFile(zoneDefinitionFilePath, std::fstream::out | std::fstream::binary); + if (!zoneDefinitionFile.is_open()) + { + con::error("Failed to open file for zone definition file of zone \"{}\".", zone.m_name); + return false; + } + + const auto* zoneDefWriter = IZoneDefWriter::GetZoneDefWriterForGame(zone.m_game_id); + zoneDefWriter->WriteZoneDef(zoneDefinitionFile, zone, m_args.m_use_gdt); + + zoneDefinitionFile.close(); + + return true; + } + + static bool OpenGdtFile(const Zone& zone, const fs::path& outputFolder, std::ofstream& stream) + { + auto gdtFilePath(outputFolder); + gdtFilePath.append("source_data"); + + fs::create_directories(gdtFilePath); + + gdtFilePath.append(zone.m_name); + gdtFilePath.replace_extension(".gdt"); + + stream = std::ofstream(gdtFilePath, std::fstream::out | std::fstream::binary); + if (!stream.is_open()) + { + con::error("Failed to open file for zone definition file of zone \"{}\".", zone.m_name); + return false; + } + + return true; + } + + void UpdateAssetIncludesAndExcludes(const AssetDumpingContext& context) const + { + const auto assetTypeCount = context.m_zone.m_pools->GetAssetTypeCount(); + + ObjWriting::Configuration.AssetTypesToHandleBitfield = std::vector(assetTypeCount); + + std::vector handledSpecifiedAssets(m_args.m_specified_asset_types.size()); + for (auto i = 0u; i < assetTypeCount; i++) + { + const auto assetTypeName = std::string(*context.m_zone.m_pools->GetAssetTypeName(i)); + + const auto foundSpecifiedEntry = m_args.m_specified_asset_type_map.find(assetTypeName); + if (foundSpecifiedEntry != m_args.m_specified_asset_type_map.end()) + { + ObjWriting::Configuration.AssetTypesToHandleBitfield[i] = m_args.m_asset_type_handling == UnlinkerArgs::AssetTypeHandling::INCLUDE; + assert(foundSpecifiedEntry->second < handledSpecifiedAssets.size()); + handledSpecifiedAssets[foundSpecifiedEntry->second] = true; + } + else + ObjWriting::Configuration.AssetTypesToHandleBitfield[i] = m_args.m_asset_type_handling == UnlinkerArgs::AssetTypeHandling::EXCLUDE; + } + + auto anySpecifiedValueInvalid = false; + for (auto i = 0u; i < handledSpecifiedAssets.size(); i++) + { + if (!handledSpecifiedAssets[i]) + { + con::error("Unknown asset type \"{}\"", m_args.m_specified_asset_types[i]); + anySpecifiedValueInvalid = true; + } + } + + if (anySpecifiedValueInvalid) + { + con::error("Valid asset types are:"); + + auto first = true; + std::ostringstream ss; + for (auto i = 0u; i < assetTypeCount; i++) + { + const auto assetTypeName = std::string(*context.m_zone.m_pools->GetAssetTypeName(i)); + + if (first) + first = false; + else + ss << ", "; + ss << assetTypeName; + } + con::error(ss.str()); + } + } + + /** + * \brief Performs the tasks specified by the command line arguments on the specified zone. + * \param searchPath The search path for obj data. + * \param zone The zone to handle. + * \return \c true if handling the zone was successful, otherwise \c false + */ + bool HandleZone(ISearchPath& searchPath, Zone& zone) const + { + if (m_args.m_task == UnlinkerArgs::ProcessingTask::LIST) + { + const ContentPrinter printer(zone); + printer.PrintContent(); + } + else if (m_args.m_task == UnlinkerArgs::ProcessingTask::DUMP) + { + const auto outputFolderPathStr = m_args.GetOutputFolderPathForZone(zone); + const fs::path outputFolderPath(outputFolderPathStr); + fs::create_directories(outputFolderPath); + + fs::path zoneDefinitionFileFolder(outputFolderPath); + zoneDefinitionFileFolder.append("zone_source"); + fs::create_directories(zoneDefinitionFileFolder); + + if (!WriteZoneDefinitionFile(zone, zoneDefinitionFileFolder)) + return false; + + OutputPathFilesystem outputFolderOutputPath(outputFolderPath); + AssetDumpingContext context(zone, outputFolderPathStr, outputFolderOutputPath, searchPath, std::nullopt); + + std::ofstream gdtStream; + if (m_args.m_use_gdt) + { + if (!OpenGdtFile(zone, outputFolderPath, gdtStream)) + return false; + auto gdt = std::make_unique(gdtStream); + gdt->BeginStream(); + + const auto* game = IGame::GetGameById(zone.m_game_id); + gdt->WriteVersion(GdtVersion(game->GetShortName(), 1)); + + context.m_gdt = std::move(gdt); + } + + UpdateAssetIncludesAndExcludes(context); + + const auto* objWriter = IObjWriter::GetObjWriterForGame(zone.m_game_id); + + auto result = objWriter->DumpZone(context); + + if (m_args.m_use_gdt) + { + context.m_gdt->EndStream(); + gdtStream.close(); + } + + if (!result) + { + con::error("Dumping zone failed!"); + return false; + } + } + + return true; + } + + bool LoadZones(UnlinkerPaths& paths) + { + for (const auto& zonePath : m_args.m_zones_to_load) + { + if (!fs::is_regular_file(zonePath)) + { + con::error("Could not find file \"{}\".", zonePath); + continue; + } + + auto absoluteZoneDirectory = absolute(std::filesystem::path(zonePath).remove_filename()).string(); + + auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory); + auto maybeZone = ZoneLoading::LoadZone(zonePath, std::nullopt); + if (!maybeZone) + { + con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error()); + return false; + } + + auto zone = std::move(*maybeZone); + + con::debug("Loaded zone \"{}\"", zone->m_name); + + if (ShouldLoadObj()) + { + const auto* objLoader = IObjLoader::GetObjLoaderForGame(zone->m_game_id); + objLoader->LoadReferencedContainersForZone(*searchPathsForZone, *zone); + } + + 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; + + // Copy zone name since we deallocate before logging + const auto zoneName = loadedZone->m_name; + + if (ShouldLoadObj()) + { + const auto* objLoader = IObjLoader::GetObjLoaderForGame(loadedZone->m_game_id); + objLoader->UnloadContainersOfZone(*loadedZone); + } + + loadedZone.reset(); + + con::debug("Unloaded zone \"{}\"", zoneName); + } + m_loaded_zones.clear(); + } + + bool UnlinkZones(UnlinkerPaths& paths) const + { + for (const auto& zonePath : m_args.m_zones_to_unlink) + { + if (!fs::is_regular_file(zonePath)) + { + con::error("Could not find file \"{}\".", zonePath); + continue; + } + + auto zoneDirectory = fs::path(zonePath).remove_filename(); + if (zoneDirectory.empty()) + zoneDirectory = fs::current_path(); + auto absoluteZoneDirectory = absolute(zoneDirectory).string(); + + auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory); + + auto maybeZone = ZoneLoading::LoadZone(zonePath, std::nullopt); + if (!maybeZone) + { + con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error()); + return false; + } + + auto zone = std::move(*maybeZone); + + con::debug("Loaded zone \"{}\"", zone->m_name); + + const auto* objLoader = IObjLoader::GetObjLoaderForGame(zone->m_game_id); + if (ShouldLoadObj()) + objLoader->LoadReferencedContainersForZone(*searchPathsForZone, *zone); + + if (!HandleZone(*searchPathsForZone, *zone)) + return false; + + if (ShouldLoadObj()) + objLoader->UnloadContainersOfZone(*zone); + + // Copy zone name for using it after freeing the zone + std::string zoneName = zone->m_name; + zone.reset(); + con::debug("Unloaded zone \"{}\"", zoneName); + } + + return true; + } + + UnlinkerArgs m_args; + std::vector> m_loaded_zones; + }; +} // namespace + +std::unique_ptr Unlinker::Create(UnlinkerArgs args) +{ + return std::make_unique(std::move(args)); +} diff --git a/src/Unlinking/Unlinker.h b/src/Unlinking/Unlinker.h new file mode 100644 index 00000000..83f6aaaf --- /dev/null +++ b/src/Unlinking/Unlinker.h @@ -0,0 +1,19 @@ +#pragma once + +#include "UnlinkerArgs.h" + +#include + +class Unlinker +{ +public: + virtual ~Unlinker() = default; + + static std::unique_ptr Create(UnlinkerArgs args); + + /** + * \brief Starts the Unlinker application logic. + * \return \c true if the application was successful or \c false if an error occurred. + */ + virtual bool Start() = 0; +}; diff --git a/src/Unlinker/UnlinkerArgs.cpp b/src/Unlinking/UnlinkerArgs.cpp similarity index 100% rename from src/Unlinker/UnlinkerArgs.cpp rename to src/Unlinking/UnlinkerArgs.cpp diff --git a/src/Unlinker/UnlinkerArgs.h b/src/Unlinking/UnlinkerArgs.h similarity index 100% rename from src/Unlinker/UnlinkerArgs.h rename to src/Unlinking/UnlinkerArgs.h diff --git a/src/Unlinker/UnlinkerPaths.cpp b/src/Unlinking/UnlinkerPaths.cpp similarity index 100% rename from src/Unlinker/UnlinkerPaths.cpp rename to src/Unlinking/UnlinkerPaths.cpp diff --git a/src/Unlinker/UnlinkerPaths.h b/src/Unlinking/UnlinkerPaths.h similarity index 100% rename from src/Unlinker/UnlinkerPaths.h rename to src/Unlinking/UnlinkerPaths.h