From 526ea8c36ea1bd238f5a95d9adaa9ac54886ef8e Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 27 Oct 2024 19:21:47 +0100 Subject: [PATCH] refactor: rework search paths --- src/Linker/Linker.cpp | 271 +++++++++---- src/Linker/LinkerArgs.cpp | 118 +----- src/Linker/LinkerArgs.h | 38 +- src/Linker/LinkerPaths.cpp | 325 ++++++++++++++++ src/Linker/LinkerPaths.h | 74 ++++ src/Linker/LinkerSearchPaths.cpp | 190 --------- src/Linker/LinkerSearchPaths.h | 45 --- src/ObjLoading/ObjContainer/IWD/IWD.cpp | 363 ------------------ src/ObjLoading/ObjContainer/IWD/IWD.h | 36 -- src/ObjLoading/ObjLoading.cpp | 43 --- src/ObjLoading/ObjLoading.h | 21 - src/ObjLoading/SearchPath/ISearchPath.h | 3 +- src/ObjLoading/SearchPath/IWD.cpp | 337 ++++++++++++++++ src/ObjLoading/SearchPath/IWD.h | 11 + .../SearchPath/SearchPathFilesystem.cpp | 10 +- .../SearchPath/SearchPathFilesystem.h | 2 +- src/ObjLoading/SearchPath/SearchPaths.cpp | 5 +- src/ObjLoading/SearchPath/SearchPaths.h | 3 +- src/Unlinker/Unlinker.cpp | 135 +------ src/Unlinker/UnlinkerArgs.h | 4 +- src/Unlinker/UnlinkerPaths.cpp | 72 ++++ src/Unlinker/UnlinkerPaths.h | 21 + test/ObjLoadingTests/Mock/MockSearchPath.cpp | 5 +- test/ObjLoadingTests/Mock/MockSearchPath.h | 2 +- 24 files changed, 1070 insertions(+), 1064 deletions(-) create mode 100644 src/Linker/LinkerPaths.cpp create mode 100644 src/Linker/LinkerPaths.h delete mode 100644 src/Linker/LinkerSearchPaths.cpp delete mode 100644 src/Linker/LinkerSearchPaths.h delete mode 100644 src/ObjLoading/ObjContainer/IWD/IWD.cpp delete mode 100644 src/ObjLoading/ObjContainer/IWD/IWD.h create mode 100644 src/ObjLoading/SearchPath/IWD.cpp create mode 100644 src/ObjLoading/SearchPath/IWD.h create mode 100644 src/Unlinker/UnlinkerPaths.cpp create mode 100644 src/Unlinker/UnlinkerPaths.h diff --git a/src/Linker/Linker.cpp b/src/Linker/Linker.cpp index 8176014e4..c44d557d2 100644 --- a/src/Linker/Linker.cpp +++ b/src/Linker/Linker.cpp @@ -1,9 +1,8 @@ #include "Linker.h" #include "LinkerArgs.h" -#include "LinkerSearchPaths.h" +#include "LinkerPaths.h" #include "ObjContainer/IPak/IPakWriter.h" -#include "ObjContainer/IWD/IWD.h" #include "ObjContainer/SoundBank/SoundBankWriter.h" #include "ObjWriting.h" #include "SearchPath/SearchPaths.h" @@ -20,17 +19,143 @@ #include #include #include -#include +#include namespace fs = std::filesystem; -class LinkerImpl final : public Linker +namespace { - LinkerArgs m_args; - LinkerSearchPaths m_search_paths; - std::vector> m_loaded_zones; + 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; + }; - bool IncludeAdditionalZoneDefinitions(const std::string& initialFileName, ZoneDefinition& zoneDefinition, ISearchPath* sourceSearchPath) const + 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 +{ + bool IncludeAdditionalZoneDefinitions(const std::string& initialFileName, ZoneDefinition& zoneDefinition, ISearchPath& sourceSearchPath) const { std::set sourceNames; sourceNames.emplace(initialFileName); @@ -50,7 +175,7 @@ class LinkerImpl final : public Linker std::unique_ptr includeDefinition; { const auto definitionFileName = std::format("{}.zone", source); - const auto definitionStream = sourceSearchPath->Open(definitionFileName); + const auto definitionStream = sourceSearchPath.Open(definitionFileName); if (!definitionStream.IsOpen()) { std::cerr << std::format("Could not find zone definition file for project \"{}\".\n", source); @@ -80,11 +205,11 @@ class LinkerImpl final : public Linker return true; } - bool ReadAssetList(const std::string& zoneName, const GameId game, AssetList& assetList, ISearchPath* sourceSearchPath) const + bool ReadAssetList(LinkerPathManager& paths, const std::string& zoneName, const GameId game, AssetList& assetList) const { { const auto assetListFileName = std::format("assetlist/{}.csv", zoneName); - const auto assetListStream = sourceSearchPath->Open(assetListFileName); + const auto assetListStream = paths.m_source_paths.GetSearchPaths().Open(assetListFileName); if (assetListStream.IsOpen()) { @@ -102,7 +227,7 @@ class LinkerImpl final : public Linker } { - const auto zoneDefinition = ReadZoneDefinition(zoneName, sourceSearchPath); + const auto zoneDefinition = ReadZoneDefinition(paths, zoneName); if (zoneDefinition) { @@ -117,12 +242,12 @@ class LinkerImpl final : public Linker return false; } - bool IncludeAssetLists(ZoneDefinition& zoneDefinition, ISearchPath* sourceSearchPath) const + bool IncludeAssetLists(LinkerPathManager& paths, ZoneDefinition& zoneDefinition) const { for (const auto& assetListName : zoneDefinition.m_asset_lists) { AssetList assetList; - if (!ReadAssetList(assetListName, zoneDefinition.m_game, assetList, sourceSearchPath)) + if (!ReadAssetList(paths, assetListName, zoneDefinition.m_game, assetList)) { std::cerr << std::format("Failed to read asset list \"{}\"\n", assetListName); return false; @@ -134,12 +259,13 @@ class LinkerImpl final : public Linker return true; } - std::unique_ptr ReadZoneDefinition(const std::string& targetName, ISearchPath* sourceSearchPath) const + std::unique_ptr ReadZoneDefinition(LinkerPathManager& paths, const std::string& targetName) 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); + const auto definitionStream = sourceSearchPath.Open(definitionFileName); if (!definitionStream.IsOpen()) { std::cerr << std::format("Could not find zone definition file for target \"{}\".\n", targetName); @@ -159,13 +285,13 @@ class LinkerImpl final : public Linker if (!IncludeAdditionalZoneDefinitions(targetName, *zoneDefinition, sourceSearchPath)) return nullptr; - if (!IncludeAssetLists(*zoneDefinition, sourceSearchPath)) + if (!IncludeAssetLists(paths, *zoneDefinition)) return nullptr; return zoneDefinition; } - bool ProcessZoneDefinitionIgnores(const std::string& targetName, ZoneCreationContext& context, ISearchPath* sourceSearchPath) const + bool ProcessZoneDefinitionIgnores(LinkerPathManager& paths, const std::string& targetName, ZoneCreationContext& context) const { if (context.m_definition->m_ignores.empty()) return true; @@ -176,7 +302,7 @@ class LinkerImpl final : public Linker continue; std::vector assetList; - if (!ReadAssetList(ignore, context.m_definition->m_game, context.m_ignored_assets, sourceSearchPath)) + if (!ReadAssetList(paths, ignore, context.m_definition->m_game, context.m_ignored_assets)) { std::cerr << std::format("Failed to read asset listing for ignoring assets of project \"{}\".\n", ignore); return false; @@ -210,25 +336,21 @@ class LinkerImpl final : public Linker return true; } - std::unique_ptr CreateZoneForDefinition(const std::string& targetName, - ZoneDefinition& zoneDefinition, - ISearchPath* assetSearchPath, - ISearchPath* gdtSearchPath, - ISearchPath* sourceSearchPath) const + std::unique_ptr CreateZoneForDefinition(LinkerPathManager& paths, const std::string& targetName, ZoneDefinition& zoneDefinition) const { - ZoneCreationContext context(&zoneDefinition, assetSearchPath); - if (!ProcessZoneDefinitionIgnores(targetName, context, sourceSearchPath)) + ZoneCreationContext context(&zoneDefinition, &paths.m_asset_paths.GetSearchPaths()); + if (!ProcessZoneDefinitionIgnores(paths, targetName, context)) return nullptr; - if (!LoadGdtFilesFromZoneDefinition(context.m_gdt_files, zoneDefinition, gdtSearchPath)) + if (!LoadGdtFilesFromZoneDefinition(context.m_gdt_files, zoneDefinition, &paths.m_gdt_paths.GetSearchPaths())) return nullptr; const auto* creator = IZoneCreator::GetCreatorForGame(zoneDefinition.m_game); return creator->CreateZoneForDefinition(context); } - bool WriteZoneToFile(const std::string& projectName, Zone* zone) const + bool WriteZoneToFile(const LinkerPathManager& paths, const std::string& projectName, Zone* zone) const { - const fs::path zoneFolderPath(m_args.GetOutputFolderPathForProject(projectName)); + const fs::path zoneFolderPath(paths.m_linker_paths->BuildOutputFolderPath(projectName, zone->m_game->GetId())); auto zoneFilePath(zoneFolderPath); zoneFilePath.append(zone->m_name + ".ff"); @@ -256,26 +378,21 @@ class LinkerImpl final : public Linker return true; } - bool BuildFastFile(const std::string& projectName, - const std::string& targetName, - ZoneDefinition& zoneDefinition, - SearchPaths& assetSearchPaths, - SearchPaths& gdtSearchPaths, - SearchPaths& sourceSearchPaths) const + bool BuildFastFile(LinkerPathManager& paths, const std::string& projectName, const std::string& targetName, ZoneDefinition& zoneDefinition) const { - SoundBankWriter::OutputPath = fs::path(m_args.GetOutputFolderPathForProject(projectName)); + SoundBankWriter::OutputPath = fs::path(paths.m_linker_paths->BuildOutputFolderPath(projectName, zoneDefinition.m_game)); - const auto zone = CreateZoneForDefinition(targetName, zoneDefinition, &assetSearchPaths, &gdtSearchPaths, &sourceSearchPaths); + const auto zone = CreateZoneForDefinition(paths, targetName, zoneDefinition); auto result = zone != nullptr; if (zone) - result = WriteZoneToFile(projectName, zone.get()); + result = WriteZoneToFile(paths, projectName, zone.get()); return result; } - bool BuildIPak(const std::string& projectName, const ZoneDefinition& zoneDefinition, SearchPaths& assetSearchPaths) const + bool BuildIPak(const LinkerPathManager& paths, const std::string& projectName, const ZoneDefinition& zoneDefinition, SearchPaths& assetSearchPaths) const { - const fs::path ipakFolderPath(m_args.GetOutputFolderPathForProject(projectName)); + const fs::path ipakFolderPath(paths.m_linker_paths->BuildOutputFolderPath(projectName, zoneDefinition.m_game)); auto ipakFilePath(ipakFolderPath); ipakFilePath.append(std::format("{}.ipak", zoneDefinition.m_name)); @@ -309,46 +426,44 @@ class LinkerImpl final : public Linker return true; } - bool BuildReferencedTargets(const std::string& projectName, const std::string& targetName, const ZoneDefinition& zoneDefinition) + bool BuildProject(LinkerPathManager& paths, const std::string& projectName, const std::string& targetName) const { - return std::ranges::all_of(zoneDefinition.m_targets_to_build, - [this, &projectName, &targetName](const std::string& buildTargetName) - { - if (buildTargetName == targetName) - { - std::cerr << std::format("Cannot build target with same name: \"{}\"\n", targetName); - return false; - } - - std::cout << std::format("Building referenced target \"{}\"\n", buildTargetName); - return BuildProject(projectName, buildTargetName); - }); - } + std::deque targetsToBuild; + std::unordered_set alreadyBuiltTargets; - bool BuildProject(const std::string& projectName, const std::string& targetName) - { - auto sourceSearchPaths = m_search_paths.GetSourceSearchPathsForProject(projectName); + targetsToBuild.emplace_back(targetName); - const auto zoneDefinition = ReadZoneDefinition(targetName, &sourceSearchPaths); - if (!zoneDefinition) - return false; + while (!targetsToBuild.empty()) + { + const auto currentTarget = std::move(targetsToBuild.front()); + targetsToBuild.pop_front(); + alreadyBuiltTargets.emplace(currentTarget); - auto result = true; + PathProjectContext projectContext(paths, projectName); - if (!zoneDefinition->m_assets.empty()) - { - const auto& gameName = GameId_Names[static_cast(zoneDefinition->m_game)]; - auto assetSearchPaths = m_search_paths.GetAssetSearchPathsForProject(gameName, projectName); - auto gdtSearchPaths = m_search_paths.GetGdtSearchPathsForProject(gameName, projectName); + const auto zoneDefinition = ReadZoneDefinition(paths, targetName); + if (!zoneDefinition) + return false; - result = result && BuildFastFile(projectName, targetName, *zoneDefinition, assetSearchPaths, gdtSearchPaths, sourceSearchPaths); - } + PathGameContext gameContext(paths, projectName, zoneDefinition->m_game); - m_search_paths.UnloadProjectSpecificSearchPaths(); + if (!zoneDefinition->m_assets.empty()) + { + if (!BuildFastFile(paths, projectName, targetName, *zoneDefinition)) + return false; - result = result && BuildReferencedTargets(projectName, targetName, *zoneDefinition); + for (const auto& referencedTarget : zoneDefinition->m_targets_to_build) + { + if (alreadyBuiltTargets.find(referencedTarget) == alreadyBuiltTargets.end()) + { + targetsToBuild.emplace_back(referencedTarget); + std::cout << std::format("Building referenced target \"{}\"\n", referencedTarget); + } + } + } + } - return result; + return true; } bool LoadZones() @@ -434,11 +549,6 @@ class LinkerImpl final : public Linker } public: - LinkerImpl() - : m_search_paths(m_args) - { - } - bool Start(const int argc, const char** argv) override { auto shouldContinue = true; @@ -448,8 +558,7 @@ class LinkerImpl final : public Linker if (!shouldContinue) return true; - if (!m_search_paths.BuildProjectIndependentSearchPaths()) - return false; + LinkerPathManager paths(m_args); if (!LoadZones()) return false; @@ -465,7 +574,7 @@ class LinkerImpl final : public Linker break; } - if (!BuildProject(projectName, targetName)) + if (!BuildProject(paths, projectName, targetName)) { result = false; break; @@ -476,6 +585,10 @@ class LinkerImpl final : public Linker return result; } + +private: + LinkerArgs m_args; + std::vector> m_loaded_zones; }; std::unique_ptr Linker::Create() diff --git a/src/Linker/LinkerArgs.cpp b/src/Linker/LinkerArgs.cpp index 323fd1560..a0d0f7fef 100644 --- a/src/Linker/LinkerArgs.cpp +++ b/src/Linker/LinkerArgs.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include namespace fs = std::filesystem; @@ -129,13 +128,7 @@ const CommandLineOption* const COMMAND_LINE_OPTIONS[]{ LinkerArgs::LinkerArgs() : m_verbose(false), - m_base_folder_depends_on_project(false), - m_out_folder_depends_on_project(false), - m_argument_parser(COMMAND_LINE_OPTIONS, std::extent_v), - m_bin_pattern(R"(\?bin\?)"), - m_base_pattern(R"(\?base\?)"), - m_game_pattern(R"(\?game\?)"), - m_project_pattern(R"(\?project\?)") + m_argument_parser(COMMAND_LINE_OPTIONS, std::extent_v) { } @@ -172,76 +165,6 @@ void LinkerArgs::SetVerbose(const bool isVerbose) ObjWriting::Configuration.Verbose = isVerbose; } -std::string LinkerArgs::GetBasePathForProject(const std::string& projectName) const -{ - return std::regex_replace(m_base_folder, m_project_pattern, projectName); -} - -void LinkerArgs::SetDefaultBasePath() -{ - const auto currentDir = fs::absolute(fs::current_path()); - - if (currentDir.filename() == "bin") - { - m_base_folder = currentDir.parent_path().string(); - } - else - { - m_base_folder = currentDir.string(); - } -} - -std::set LinkerArgs::GetProjectIndependentSearchPaths(const std::set& set) const -{ - std::set out; - - for (const auto& path : set) - { - if (path.find(PATTERN_GAME) != std::string::npos) - continue; - if (path.find(PATTERN_PROJECT) != std::string::npos) - continue; - - if (path.find(PATTERN_BASE) != std::string::npos) - { - if (m_base_folder_depends_on_project) - continue; - - out.emplace(std::regex_replace(path, m_base_pattern, m_base_folder)); - } - else - { - out.emplace(path); - } - } - - return out; -} - -std::set LinkerArgs::GetSearchPathsForProject(const std::set& set, const std::string& gameName, const std::string& projectName) const -{ - std::set out; - const auto basePath = GetBasePathForProject(projectName); - - for (const auto& path : set) - { - if (path.find(PATTERN_GAME) == std::string::npos && path.find(PATTERN_PROJECT) == std::string::npos - && (!m_base_folder_depends_on_project || path.find(PATTERN_BASE) == std::string::npos)) - { - continue; - } - - fs::path p(std::regex_replace(std::regex_replace(std::regex_replace(std::regex_replace(path, m_project_pattern, projectName), m_game_pattern, gameName), - m_base_pattern, - basePath), - m_bin_pattern, - m_bin_folder)); - out.emplace(p.make_preferred().string()); - } - - return out; -} - bool LinkerArgs::ParseArgs(const int argc, const char** argv, bool& shouldContinue) { shouldContinue = true; @@ -284,15 +207,13 @@ bool LinkerArgs::ParseArgs(const int argc, const char** argv, bool& shouldContin if (m_argument_parser.IsOptionSpecified(OPTION_BASE_FOLDER)) m_base_folder = m_argument_parser.GetValueForOption(OPTION_BASE_FOLDER); else - SetDefaultBasePath(); - m_base_folder_depends_on_project = m_base_folder.find(PATTERN_GAME) != std::string::npos || m_base_folder.find(PATTERN_PROJECT) != std::string::npos; + m_base_folder = DEFAULT_BASE_FOLDER; // --output-folder if (m_argument_parser.IsOptionSpecified(OPTION_OUTPUT_FOLDER)) m_out_folder = m_argument_parser.GetValueForOption(OPTION_OUTPUT_FOLDER); else m_out_folder = DEFAULT_OUTPUT_FOLDER; - m_out_folder_depends_on_project = m_out_folder.find(PATTERN_PROJECT) != std::string::npos; // --asset-search-path if (m_argument_parser.IsOptionSpecified(OPTION_ASSET_SEARCH_PATH)) @@ -358,38 +279,3 @@ bool LinkerArgs::ParseArgs(const int argc, const char** argv, bool& shouldContin return true; } - -std::string LinkerArgs::GetOutputFolderPathForProject(const std::string& projectName) const -{ - return std::regex_replace(std::regex_replace(m_out_folder, m_project_pattern, projectName), m_base_pattern, GetBasePathForProject(projectName)); -} - -std::set LinkerArgs::GetProjectIndependentAssetSearchPaths() const -{ - return GetProjectIndependentSearchPaths(m_asset_search_paths); -} - -std::set LinkerArgs::GetProjectIndependentGdtSearchPaths() const -{ - return GetProjectIndependentSearchPaths(m_gdt_search_paths); -} - -std::set LinkerArgs::GetProjectIndependentSourceSearchPaths() const -{ - return GetProjectIndependentSearchPaths(m_source_search_paths); -} - -std::set LinkerArgs::GetAssetSearchPathsForProject(const std::string& gameName, const std::string& projectName) const -{ - return GetSearchPathsForProject(m_asset_search_paths, gameName, projectName); -} - -std::set LinkerArgs::GetGdtSearchPathsForProject(const std::string& gameName, const std::string& projectName) const -{ - return GetSearchPathsForProject(m_gdt_search_paths, gameName, projectName); -} - -std::set LinkerArgs::GetSourceSearchPathsForProject(const std::string& projectName) const -{ - return GetSearchPathsForProject(m_source_search_paths, "", projectName); -} diff --git a/src/Linker/LinkerArgs.h b/src/Linker/LinkerArgs.h index 41fb32dda..fe2b8f4b0 100644 --- a/src/Linker/LinkerArgs.h +++ b/src/Linker/LinkerArgs.h @@ -1,22 +1,14 @@ #pragma once #include "Utils/Arguments/ArgumentParser.h" -#include "Utils/ClassUtils.h" -#include "Zone/Zone.h" -#include #include #include class LinkerArgs { public: - static constexpr auto PATTERN_BIN = "?bin?"; - static constexpr auto PATTERN_BASE = "?base?"; - static constexpr auto PATTERN_GAME = "?game?"; - static constexpr auto PATTERN_PROJECT = "?project?"; - static constexpr auto DEFAULT_BASE_FOLDER = "."; - static constexpr auto DEFAULT_BASE_FOLDER_MOD_TOOLS = ".."; + static constexpr auto DEFAULT_CACHE_FOLDER = "?base?/.oat/cache/?project?"; static constexpr auto DEFAULT_OUTPUT_FOLDER = "?base?/zone_out/?project?"; static constexpr auto DEFAULT_ASSET_SEARCH_PATH = "?bin?/raw/?game?;?base?/raw;?base?/raw/?game?;?base?/zone_raw/?project?"; static constexpr auto DEFAULT_GDT_SEARCH_PATH = "?base?/source_data;?base?/zone_raw/?project?/source_data"; @@ -25,21 +17,6 @@ class LinkerArgs LinkerArgs(); bool ParseArgs(int argc, const char** argv, bool& shouldContinue); - /** - * \brief Converts the output path specified by command line arguments to a path applies for the specified project. - * \param projectName The name of the project to resolve the path input for. - * \return An output path for the project based on the user input. - */ - _NODISCARD std::string GetOutputFolderPathForProject(const std::string& projectName) const; - - _NODISCARD std::set GetProjectIndependentAssetSearchPaths() const; - _NODISCARD std::set GetProjectIndependentGdtSearchPaths() const; - _NODISCARD std::set GetProjectIndependentSourceSearchPaths() const; - - _NODISCARD std::set GetAssetSearchPathsForProject(const std::string& gameName, const std::string& projectName) const; - _NODISCARD std::set GetGdtSearchPathsForProject(const std::string& gameName, const std::string& projectName) const; - _NODISCARD std::set GetSourceSearchPathsForProject(const std::string& projectName) const; - bool m_verbose; std::vector m_zones_to_load; @@ -48,8 +25,6 @@ class LinkerArgs std::string m_bin_folder; std::string m_base_folder; std::string m_out_folder; - bool m_base_folder_depends_on_project; - bool m_out_folder_depends_on_project; std::set m_asset_search_paths; std::set m_gdt_search_paths; @@ -63,18 +38,7 @@ class LinkerArgs static void PrintVersion(); void SetBinFolder(const char* argv0); - void SetVerbose(bool isVerbose); - _NODISCARD std::string GetBasePathForProject(const std::string& projectName) const; - void SetDefaultBasePath(); - _NODISCARD std::set GetProjectIndependentSearchPaths(const std::set& set) const; - _NODISCARD std::set - GetSearchPathsForProject(const std::set& set, const std::string& gameName, const std::string& projectName) const; - ArgumentParser m_argument_parser; - std::regex m_bin_pattern; - std::regex m_base_pattern; - std::regex m_game_pattern; - std::regex m_project_pattern; }; diff --git a/src/Linker/LinkerPaths.cpp b/src/Linker/LinkerPaths.cpp new file mode 100644 index 000000000..2e8eb6a78 --- /dev/null +++ b/src/Linker/LinkerPaths.cpp @@ -0,0 +1,325 @@ +#include "LinkerPaths.h" + +#include "SearchPath/IWD.h" +#include "SearchPath/SearchPathFilesystem.h" +#include "SearchPath/SearchPaths.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace +{ + enum class PathTemplateParameterType : std::uint8_t + { + BIN = 1 << 0, + BASE = 1 << 1, + PROJECT = 1 << 2, + GAME = 1 << 3, + }; + + class LinkerPathTemplate + { + static constexpr auto PATTERN_BIN = "?bin?"; + static constexpr auto PATTERN_BASE = "?base?"; + static constexpr auto PATTERN_GAME = "?game?"; + static constexpr auto PATTERN_PROJECT = "?project?"; + + struct Pattern + { + const char* m_str; + PathTemplateParameterType m_type; + }; + + static constexpr Pattern PATTERNS[]{ + {PATTERN_BIN, PathTemplateParameterType::BIN }, + {PATTERN_BASE, PathTemplateParameterType::BASE }, + {PATTERN_GAME, PathTemplateParameterType::GAME }, + {PATTERN_PROJECT, PathTemplateParameterType::PROJECT}, + }; + + public: + LinkerPathTemplate() + : m_parameter_type_flags(0u) + { + } + + void CreateFromString(const std::string& templateString) + { + const auto templateStringLength = templateString.size(); + auto partStart = 0u; + for (auto i = 0u; i < templateStringLength; i++) + { + if (templateString[i] != '?') + continue; + + for (const auto& pattern : PATTERNS) + { + const auto patternLength = std::strlen(pattern.m_str); + if (templateString.compare(i, patternLength, pattern.m_str) == 0) + { + m_parts.emplace_back(templateString.substr(partStart, i - partStart)); + m_parameters.emplace_back(pattern.m_type); + m_parameter_type_flags |= static_cast>(pattern.m_type); + i += patternLength; + + partStart = i; + break; + } + } + } + + if (partStart < templateStringLength) + m_parts.emplace_back(templateString.substr(partStart, templateStringLength - partStart)); + } + + [[nodiscard]] std::string + Render(const std::string& binDir, const std::string& baseDir, const std::string& projectName, const std::string& gameName) const + { + if (m_parts.empty()) + return ""; + + if (m_parameters.empty()) + return m_parts[0]; + + std::ostringstream ss; + ss << m_parts[0]; + + const auto partsCount = m_parts.size(); + const auto parameterCount = m_parameters.size(); + for (auto parameterIndex = 0u; parameterIndex < parameterCount; parameterIndex++) + { + switch (m_parameters[parameterIndex]) + { + case PathTemplateParameterType::BIN: + ss << binDir; + break; + case PathTemplateParameterType::BASE: + ss << baseDir; + break; + case PathTemplateParameterType::PROJECT: + ss << projectName; + break; + case PathTemplateParameterType::GAME: + ss << gameName; + break; + default: + assert(false); + break; + } + + if (parameterIndex + 1 < partsCount) + ss << m_parts[parameterIndex + 1]; + } + + return ss.str(); + } + + [[nodiscard]] bool CanRender(const std::underlying_type_t availableParameters) const + { + return (m_parameter_type_flags & ~availableParameters) == 0; + } + + private: + std::vector m_parts; + std::vector m_parameters; + std::underlying_type_t m_parameter_type_flags; + }; + + class LinkerSearchPathBuilder final : public ILinkerSearchPathBuilder + { + static constexpr auto INDEPENDENT_MASK = static_cast(PathTemplateParameterType::BIN) | static_cast(PathTemplateParameterType::BASE); + static constexpr auto PROJECT_MASK = static_cast(PathTemplateParameterType::BIN) | static_cast(PathTemplateParameterType::BASE) + | static_cast(PathTemplateParameterType::PROJECT); + static constexpr auto GAME_MASK = static_cast(PathTemplateParameterType::BIN) | static_cast(PathTemplateParameterType::BASE) + | static_cast(PathTemplateParameterType::PROJECT) | static_cast(PathTemplateParameterType::GAME); + + public: + LinkerSearchPathBuilder(const char* typeName, const std::string& binDir, const std::string& baseDir) + : m_type_name(typeName), + m_bin_dir(binDir), + m_base_dir(baseDir) + { + } + + void BuildFromArgs(const std::set& templates) + { + m_templates.reserve(templates.size()); + for (const auto& templateString : templates) + { + LinkerPathTemplate templateStruct; + templateStruct.CreateFromString(templateString); + m_templates.emplace_back(std::move(templateStruct)); + } + } + + [[nodiscard]] std::unique_ptr BuildIndependentSearchPaths() const override + { + SearchPaths searchPaths; + auto hasSearchPath = false; + + for (const auto& curTemplate : m_templates) + { + if (curTemplate.CanRender(INDEPENDENT_MASK)) + { + auto renderedTemplate = curTemplate.Render(m_bin_dir, m_base_dir, std::string(), std::string()); + if (AddSearchPath(searchPaths, renderedTemplate)) + hasSearchPath = true; + } + } + + if (hasSearchPath) + return std::make_unique(std::move(searchPaths)); + + return {}; + } + + [[nodiscard]] std::unique_ptr BuildSearchPathsSpecificToProject(const std::string& projectName) const override + { + SearchPaths searchPaths; + auto hasSearchPath = false; + + for (const auto& curTemplate : m_templates) + { + if (!curTemplate.CanRender(INDEPENDENT_MASK) && curTemplate.CanRender(PROJECT_MASK)) + { + auto renderedTemplate = curTemplate.Render(m_bin_dir, m_base_dir, projectName, std::string()); + if (AddSearchPath(searchPaths, renderedTemplate)) + hasSearchPath = true; + } + } + + if (hasSearchPath) + return std::make_unique(std::move(searchPaths)); + + return {}; + } + + [[nodiscard]] std::unique_ptr BuildSearchPathsSpecificToProjectAndGame(const std::string& projectName, GameId game) const override + { + SearchPaths searchPaths; + auto hasSearchPath = false; + + for (const auto& curTemplate : m_templates) + { + if (!curTemplate.CanRender(PROJECT_MASK) && curTemplate.CanRender(GAME_MASK)) + { + auto renderedTemplate = curTemplate.Render(m_bin_dir, m_base_dir, projectName, GameId_Names[static_cast(game)]); + if (AddSearchPath(searchPaths, renderedTemplate)) + hasSearchPath = true; + } + } + + if (hasSearchPath) + return std::make_unique(std::move(searchPaths)); + + return {}; + } + + private: + bool AddSearchPath(SearchPaths& searchPaths, const std::string& path) const + { + if (!fs::is_directory(path)) + { + std::cout << std::format("Adding {} search path (Not found): {}\n", m_type_name, path); + return false; + } + + std::cout << std::format("Adding {} search path: {}\n", m_type_name, path); + searchPaths.CommitSearchPath(std::make_unique(path)); + return true; + } + + const char* m_type_name; + std::vector m_templates; + const std::string& m_bin_dir; + const std::string& m_base_dir; + }; + + class LinkerPaths final : public ILinkerPaths + { + public: + LinkerPaths(std::string binDir, + std::string baseDir, + LinkerSearchPathBuilder assetSearchPaths, + LinkerSearchPathBuilder gdtSearchPaths, + LinkerSearchPathBuilder sourceSearchPaths, + LinkerPathTemplate cacheTemplate, + LinkerPathTemplate outTemplate) + : m_bin_dir(std::move(binDir)), + m_base_dir(std::move(baseDir)), + m_asset_search_paths(std::move(assetSearchPaths)), + m_gdt_search_paths(std::move(gdtSearchPaths)), + m_source_search_paths(std::move(sourceSearchPaths)), + m_cache_template(std::move(cacheTemplate)), + m_out_template(std::move(outTemplate)) + { + } + + [[nodiscard]] const ILinkerSearchPathBuilder& AssetSearchPaths() const override + { + return m_asset_search_paths; + } + + [[nodiscard]] const ILinkerSearchPathBuilder& GdtSearchPaths() const override + { + return m_gdt_search_paths; + } + + [[nodiscard]] const ILinkerSearchPathBuilder& SourceSearchPaths() const override + { + return m_source_search_paths; + } + + [[nodiscard]] std::string BuildCacheFolderPath(const std::string& projectName, GameId game) const override + { + return m_cache_template.Render(m_bin_dir, m_base_dir, projectName, GameId_Names[static_cast(game)]); + } + + [[nodiscard]] std::string BuildOutputFolderPath(const std::string& projectName, GameId game) const override + { + return m_out_template.Render(m_bin_dir, m_base_dir, projectName, GameId_Names[static_cast(game)]); + } + + private: + std::string m_bin_dir; + std::string m_base_dir; + LinkerSearchPathBuilder m_asset_search_paths; + LinkerSearchPathBuilder m_gdt_search_paths; + LinkerSearchPathBuilder m_source_search_paths; + LinkerPathTemplate m_cache_template; + LinkerPathTemplate m_out_template; + }; +} // namespace + +std::unique_ptr ILinkerPaths::FromArgs(const LinkerArgs& args) +{ + LinkerSearchPathBuilder assetSearchPaths("asset", args.m_bin_folder, args.m_base_folder); + assetSearchPaths.BuildFromArgs(args.m_asset_search_paths); + + LinkerSearchPathBuilder gdtSearchPaths("gdt", args.m_bin_folder, args.m_base_folder); + gdtSearchPaths.BuildFromArgs(args.m_gdt_search_paths); + + LinkerSearchPathBuilder sourceSearchPaths("source", args.m_bin_folder, args.m_base_folder); + sourceSearchPaths.BuildFromArgs(args.m_source_search_paths); + + LinkerPathTemplate cacheTemplate; + cacheTemplate.CreateFromString(args.DEFAULT_CACHE_FOLDER); + + LinkerPathTemplate outTemplate; + outTemplate.CreateFromString(args.m_out_folder); + + return std::make_unique(args.m_bin_folder, + args.m_base_folder, + std::move(assetSearchPaths), + std::move(gdtSearchPaths), + std::move(sourceSearchPaths), + std::move(cacheTemplate), + std::move(outTemplate)); +} diff --git a/src/Linker/LinkerPaths.h b/src/Linker/LinkerPaths.h new file mode 100644 index 000000000..d04641282 --- /dev/null +++ b/src/Linker/LinkerPaths.h @@ -0,0 +1,74 @@ +#pragma once + +#include "Game/IGame.h" +#include "LinkerArgs.h" +#include "SearchPath/ISearchPath.h" + +#include + +class ILinkerSearchPathBuilder +{ +public: + ILinkerSearchPathBuilder() = default; + virtual ~ILinkerSearchPathBuilder() = default; + ILinkerSearchPathBuilder(const ILinkerSearchPathBuilder& other) = default; + ILinkerSearchPathBuilder(ILinkerSearchPathBuilder&& other) noexcept = default; + ILinkerSearchPathBuilder& operator=(const ILinkerSearchPathBuilder& other) = default; + ILinkerSearchPathBuilder& operator=(ILinkerSearchPathBuilder&& other) noexcept = default; + + [[nodiscard]] virtual std::unique_ptr BuildIndependentSearchPaths() const = 0; + [[nodiscard]] virtual std::unique_ptr BuildSearchPathsSpecificToProject(const std::string& projectName) const = 0; + [[nodiscard]] virtual std::unique_ptr BuildSearchPathsSpecificToProjectAndGame(const std::string& projectName, GameId game) const = 0; +}; + +class ILinkerPaths +{ +public: + ILinkerPaths() = default; + virtual ~ILinkerPaths() = default; + ILinkerPaths(const ILinkerPaths& other) = default; + ILinkerPaths(ILinkerPaths&& other) noexcept = default; + ILinkerPaths& operator=(const ILinkerPaths& other) = default; + ILinkerPaths& operator=(ILinkerPaths&& other) noexcept = default; + + /** + * \brief Creates linker search paths based on templates from user specified args. + * \param args The user specified args. + * \return Linker search paths based on user specified templates. + */ + static std::unique_ptr FromArgs(const LinkerArgs& args); + + /** + * \brief Grants access to the builder for asset search paths. + * \return A builder instance for building asset search paths. + */ + [[nodiscard]] virtual const ILinkerSearchPathBuilder& AssetSearchPaths() const = 0; + + /** + * \brief Grants access to the builder for gdt search paths. + * \return A builder instance for building gdt search paths. + */ + [[nodiscard]] virtual const ILinkerSearchPathBuilder& GdtSearchPaths() const = 0; + + /** + * \brief Grants access to the builder for source search paths. + * \return A builder instance for building source search paths. + */ + [[nodiscard]] virtual const ILinkerSearchPathBuilder& SourceSearchPaths() const = 0; + + /** + * \brief Builds the cache path based on the specified information. + * \param projectName The name of the project to resolve the path input for. + * \param game The game to resolve the path input for. + * \return A cache path based on the input and preconfigured template. + */ + [[nodiscard]] virtual std::string BuildCacheFolderPath(const std::string& projectName, GameId game) const = 0; + + /** + * \brief Builds the output path based on the specified information. + * \param projectName The name of the project to resolve the path input for. + * \param game The game to resolve the path input for. + * \return An output path based on the input and preconfigured template. + */ + [[nodiscard]] virtual std::string BuildOutputFolderPath(const std::string& projectName, GameId game) const = 0; +}; diff --git a/src/Linker/LinkerSearchPaths.cpp b/src/Linker/LinkerSearchPaths.cpp deleted file mode 100644 index c3dd66686..000000000 --- a/src/Linker/LinkerSearchPaths.cpp +++ /dev/null @@ -1,190 +0,0 @@ -#include "LinkerSearchPaths.h" - -#include "ObjContainer/IWD/IWD.h" -#include "ObjLoading.h" -#include "SearchPath/SearchPathFilesystem.h" - -#include -#include -#include - -namespace fs = std::filesystem; - -LinkerSearchPaths::LinkerSearchPaths(const LinkerArgs& args) - : m_args(args) -{ -} - -void LinkerSearchPaths::LoadSearchPath(ISearchPath& searchPath) const -{ - if (m_args.m_verbose) - { - std::cout << std::format("Loading search path: \"{}\"\n", searchPath.GetPath()); - } - - ObjLoading::LoadIWDsInSearchPath(searchPath); -} - -void LinkerSearchPaths::UnloadSearchPath(ISearchPath& searchPath) const -{ - if (m_args.m_verbose) - { - std::cout << std::format("Unloading search path: \"{}\"\n", searchPath.GetPath()); - } - - ObjLoading::UnloadIWDsInSearchPath(searchPath); -} - -SearchPaths LinkerSearchPaths::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 << std::format("Adding asset search path (Not found): {}\n", absolutePath.string()); - continue; - } - - if (m_args.m_verbose) - std::cout << std::format("Adding asset search path: {}\n", absolutePath.string()); - - auto searchPath = std::make_unique(searchPathStr); - LoadSearchPath(*searchPath); - 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 LinkerSearchPaths::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 << std::format("Adding gdt search path (Not found): {}\n", absolutePath.string()); - continue; - } - - if (m_args.m_verbose) - std::cout << std::format("Adding gdt search path: {}\n", absolutePath.string()); - - searchPathsForProject.CommitSearchPath(std::make_unique(searchPathStr)); - } - - searchPathsForProject.IncludeSearchPath(&m_gdt_search_paths); - - return searchPathsForProject; -} - -SearchPaths LinkerSearchPaths::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 << std::format("Adding source search path (Not found): {}\n", absolutePath.string()); - continue; - } - - if (m_args.m_verbose) - std::cout << std::format("Adding source search path: {}\n", absolutePath.string()); - - searchPathsForProject.CommitSearchPath(std::make_unique(searchPathStr)); - } - - searchPathsForProject.IncludeSearchPath(&m_source_search_paths); - - return searchPathsForProject; -} - -bool LinkerSearchPaths::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 << std::format("Adding asset search path (Not found): {}\n", absolutePath.string()); - continue; - } - - if (m_args.m_verbose) - std::cout << std::format("Adding asset search path: {}\n", absolutePath.string()); - - auto searchPath = std::make_unique(absolutePath.string()); - LoadSearchPath(*searchPath); - 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 << std::format("Loading gdt search path (Not found): {}\n", absolutePath.string()); - continue; - } - - if (m_args.m_verbose) - std::cout << std::format("Adding gdt search path: {}\n", absolutePath.string()); - - 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 << std::format("Loading source search path (Not found): {}\n", absolutePath.string()); - continue; - } - - if (m_args.m_verbose) - std::cout << std::format("Adding source search path: {}\n", absolutePath.string()); - - m_source_search_paths.CommitSearchPath(std::make_unique(absolutePath.string())); - } - - return true; -} - -void LinkerSearchPaths::UnloadProjectSpecificSearchPaths() -{ - for (const auto& loadedSearchPath : m_loaded_project_search_paths) - { - UnloadSearchPath(*loadedSearchPath); - } - - m_loaded_project_search_paths.clear(); -} diff --git a/src/Linker/LinkerSearchPaths.h b/src/Linker/LinkerSearchPaths.h deleted file mode 100644 index f570965e1..000000000 --- a/src/Linker/LinkerSearchPaths.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once -#include "LinkerArgs.h" -#include "SearchPath/SearchPaths.h" - -#include -#include - -class LinkerSearchPaths -{ -public: - explicit LinkerSearchPaths(const LinkerArgs& args); - - /** - * \brief Loads a search path. - * \param searchPath The search path to load. - */ - void LoadSearchPath(ISearchPath& searchPath) const; - - /** - * \brief Unloads a search path. - * \param searchPath The search path to unload. - */ - void UnloadSearchPath(ISearchPath& searchPath) const; - - SearchPaths GetAssetSearchPathsForProject(const std::string& gameName, const std::string& projectName); - - SearchPaths GetGdtSearchPathsForProject(const std::string& gameName, const std::string& projectName); - - SearchPaths GetSourceSearchPathsForProject(const std::string& projectName); - - /** - * \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(); - - void UnloadProjectSpecificSearchPaths(); - -private: - const 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; -}; diff --git a/src/ObjLoading/ObjContainer/IWD/IWD.cpp b/src/ObjLoading/ObjContainer/IWD/IWD.cpp deleted file mode 100644 index 8a5e1c6f3..000000000 --- a/src/ObjLoading/ObjContainer/IWD/IWD.cpp +++ /dev/null @@ -1,363 +0,0 @@ -#include "IWD.h" - -#include "ObjLoading.h" -#include "Utils/FileToZlibWrapper.h" - -#include -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -ObjContainerRepository IWD::Repository; - -class IWDFile final : public objbuf -{ -public: - class IParent - { - public: - virtual ~IParent() = default; - - virtual void OnIWDFileClose() = 0; - }; - -private: - IParent* m_parent; - bool m_open; - int64_t m_size; - unzFile m_container; - bool m_peeked; - int_type m_peek_symbol; - -public: - IWDFile(IParent* parent, const unzFile container, const int64_t size) - : m_parent(parent), - m_open(true), - m_size(size), - m_container(container), - m_peeked(false), - m_peek_symbol(0) - { - } - - ~IWDFile() override - { - if (m_open) - { - close(); - } - } - -protected: - int_type underflow() override - { - if (m_peeked) - return m_peek_symbol; - - const auto result = unzReadCurrentFile(m_container, &m_peek_symbol, 1u); - - if (result >= 0) - { - m_peeked = true; - return static_cast(m_peek_symbol); - } - - return EOF; - } - - int_type uflow() override - { - if (m_peeked) - { - m_peeked = false; - return m_peek_symbol; - } - - const auto result = unzReadCurrentFile(m_container, &m_peek_symbol, 1u); - return result >= 0 ? static_cast(m_peek_symbol) : EOF; - } - - std::streamsize xsgetn(char* ptr, std::streamsize count) override - { - if (m_peeked && count >= 1) - { - *ptr = static_cast(m_peek_symbol); - ptr++; - count--; - } - - const auto result = unzReadCurrentFile(m_container, ptr, static_cast(count)); - - return result >= 0 ? static_cast(result) : 0; - } - - pos_type seekoff(const off_type off, const std::ios_base::seekdir dir, const std::ios_base::openmode mode) override - { - const auto currentPos = unztell64(m_container); - - pos_type targetPos; - if (dir == std::ios_base::beg) - { - targetPos = off; - } - else if (dir == std::ios_base::cur) - { - targetPos = currentPos + off; - } - else - { - targetPos = m_size - off; - } - - return seekpos(targetPos, mode); - } - - pos_type seekpos(const pos_type pos, const std::ios_base::openmode mode) override - { - const auto currentPos = unztell64(m_container); - - if (static_cast(currentPos) < pos) - { - auto skipAmount = pos - static_cast(currentPos); - while (skipAmount > 0) - { - char temp[1024]; - const auto toRead = skipAmount > sizeof(temp) ? sizeof(temp) : static_cast(skipAmount); - unzReadCurrentFile(m_container, temp, toRead); - skipAmount -= toRead; - } - - return pos; - } - - if (currentPos == pos) - { - // This is fine - return currentPos; - } - - return std::streampos(-1); - } - -public: - _NODISCARD bool is_open() const override - { - return m_open; - } - - bool close() override - { - unzCloseCurrentFile(m_container); - m_open = false; - - m_parent->OnIWDFileClose(); - - return true; - } -}; - -class IWD::Impl : public ISearchPath, public IObjContainer, public IWDFile::IParent -{ - class IWDEntry - { - public: - int64_t m_size{}; - unz_file_pos m_file_pos{}; - }; - - std::string m_path; - std::unique_ptr m_stream; - unzFile m_unz_file; - - IWDFile* m_last_file; - - std::map m_entry_map; - -public: - Impl(std::string path, std::unique_ptr stream) - : m_path(std::move(path)), - m_stream(std::move(stream)), - m_unz_file(nullptr), - m_last_file(nullptr) - { - } - - ~Impl() override - { - if (m_unz_file != nullptr) - { - unzClose(m_unz_file); - m_unz_file = nullptr; - } - } - - Impl(const Impl& other) = delete; - Impl(Impl&& other) noexcept = default; - Impl& operator=(const Impl& other) = delete; - Impl& operator=(Impl&& other) noexcept = default; - - bool Initialize() - { - auto ioFunctions = FileToZlibWrapper::CreateFunctions32ForFile(m_stream.get()); - m_unz_file = unzOpen2("", &ioFunctions); - - if (m_unz_file == nullptr) - { - printf("Could not open IWD \"%s\"\n", m_path.c_str()); - return false; - } - - auto ret = unzGoToFirstFile(m_unz_file); - while (ret == Z_OK) - { - unz_file_info64 info; - char fileNameBuffer[256]; - unzGetCurrentFileInfo64(m_unz_file, &info, fileNameBuffer, sizeof(fileNameBuffer), nullptr, 0, nullptr, 0); - - std::string fileName(fileNameBuffer); - std::filesystem::path path(fileName); - - if (path.has_filename()) - { - IWDEntry entry; - entry.m_size = info.uncompressed_size; - unzGetFilePos(m_unz_file, &entry.m_file_pos); - m_entry_map.emplace(std::move(fileName), entry); - } - - ret = unzGoToNextFile(m_unz_file); - } - - if (ObjLoading::Configuration.Verbose) - { - printf("Loaded IWD \"%s\" with %u entries\n", m_path.c_str(), m_entry_map.size()); - } - - return true; - } - - SearchPathOpenFile Open(const std::string& fileName) override - { - if (m_unz_file == nullptr) - { - return SearchPathOpenFile(); - } - - auto iwdFilename = fileName; - std::ranges::replace(iwdFilename, '\\', '/'); - - const auto iwdEntry = m_entry_map.find(iwdFilename); - - if (iwdEntry != m_entry_map.end()) - { - if (m_last_file != nullptr) - { - throw std::runtime_error("Trying to open new IWD file while last one was not yet closed."); - } - - auto pos = iwdEntry->second.m_file_pos; - unzGoToFilePos(m_unz_file, &pos); - - if (unzOpenCurrentFile(m_unz_file) == UNZ_OK) - { - auto result = std::make_unique(this, m_unz_file, iwdEntry->second.m_size); - m_last_file = result.get(); - return SearchPathOpenFile(std::make_unique(std::move(result)), iwdEntry->second.m_size); - } - - return SearchPathOpenFile(); - } - - return SearchPathOpenFile(); - } - - std::string GetPath() override - { - return m_path; - } - - std::string GetName() override - { - return fs::path(m_path).filename().string(); - } - - void Find(const SearchPathSearchOptions& options, const std::function& callback) override - { - if (options.m_disk_files_only) - { - return; - } - - for (auto& [entryName, entry] : m_entry_map) - { - std::filesystem::path entryPath(entryName); - - if (!options.m_should_include_subdirectories && entryPath.has_parent_path()) - continue; - - if (options.m_filter_extensions && options.m_extension != entryPath.extension().string()) - continue; - - callback(entryName); - } - } - - void OnIWDFileClose() override - { - m_last_file = nullptr; - } -}; - -IWD::IWD(std::string path, std::unique_ptr stream) -{ - m_impl = new Impl(std::move(path), std::move(stream)); -} - -IWD::~IWD() -{ - delete m_impl; - m_impl = nullptr; -} - -IWD::IWD(IWD&& other) noexcept -{ - m_impl = other.m_impl; - other.m_impl = nullptr; -} - -IWD& IWD::operator=(IWD&& other) noexcept -{ - m_impl = other.m_impl; - other.m_impl = nullptr; - - return *this; -} - -bool IWD::Initialize() -{ - return m_impl->Initialize(); -} - -SearchPathOpenFile IWD::Open(const std::string& fileName) -{ - return m_impl->Open(fileName); -} - -std::string IWD::GetPath() -{ - return m_impl->GetPath(); -} - -std::string IWD::GetName() -{ - return m_impl->GetName(); -} - -void IWD::Find(const SearchPathSearchOptions& options, const std::function& callback) -{ - return m_impl->Find(options, callback); -} diff --git a/src/ObjLoading/ObjContainer/IWD/IWD.h b/src/ObjLoading/ObjContainer/IWD/IWD.h deleted file mode 100644 index 843227105..000000000 --- a/src/ObjLoading/ObjContainer/IWD/IWD.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include "ObjContainer/ObjContainerRepository.h" -#include "SearchPath/ISearchPath.h" -#include "Utils/ClassUtils.h" -#include "Utils/ObjStream.h" - -#include - -class IWD final : public ISearchPath, IObjContainer -{ - class Impl; - Impl* m_impl; - -public: - static ObjContainerRepository Repository; - - IWD(std::string path, std::unique_ptr stream); - ~IWD() override; - - IWD(const IWD& other) = delete; - IWD(IWD&& other) noexcept; - IWD& operator=(const IWD& other) = delete; - IWD& operator=(IWD&& other) noexcept; - - /** - * \brief Initializes the IWD container. - * \return \c true when initialization was successful. - */ - bool Initialize(); - - SearchPathOpenFile Open(const std::string& fileName) override; - std::string GetPath() override; - std::string GetName() override; - void Find(const SearchPathSearchOptions& options, const std::function& callback) override; -}; diff --git a/src/ObjLoading/ObjLoading.cpp b/src/ObjLoading/ObjLoading.cpp index b50fac753..1626e6f4b 100644 --- a/src/ObjLoading/ObjLoading.cpp +++ b/src/ObjLoading/ObjLoading.cpp @@ -1,46 +1,3 @@ #include "ObjLoading.h" -#include "IObjLoader.h" -#include "ObjContainer/IWD/IWD.h" -#include "SearchPath/SearchPaths.h" -#include "Utils/ObjFileStream.h" - -#include - ObjLoading::Configuration_t ObjLoading::Configuration; - -void ObjLoading::LoadIWDsInSearchPath(ISearchPath& searchPath) -{ - searchPath.Find(SearchPathSearchOptions().IncludeSubdirectories(false).FilterExtensions("iwd"), - [&searchPath](const std::string& path) - { - auto file = std::make_unique(path, std::fstream::in | std::fstream::binary); - - if (file->is_open()) - { - auto iwd = std::make_unique(path, std::move(file)); - - if (iwd->Initialize()) - { - IWD::Repository.AddContainer(std::move(iwd), &searchPath); - } - } - }); -} - -void ObjLoading::UnloadIWDsInSearchPath(ISearchPath& searchPath) -{ - IWD::Repository.RemoveContainerReferences(&searchPath); -} - -SearchPaths ObjLoading::GetIWDSearchPaths() -{ - SearchPaths iwdPaths; - - for (auto* iwd : IWD::Repository) - { - iwdPaths.IncludeSearchPath(iwd); - } - - return iwdPaths; -} diff --git a/src/ObjLoading/ObjLoading.h b/src/ObjLoading/ObjLoading.h index 372ef610d..08d9a3192 100644 --- a/src/ObjLoading/ObjLoading.h +++ b/src/ObjLoading/ObjLoading.h @@ -1,8 +1,5 @@ #pragma once -#include "SearchPath/ISearchPath.h" -#include "SearchPath/SearchPaths.h" - class ObjLoading { public: @@ -13,22 +10,4 @@ class ObjLoading bool MenuPermissiveParsing = false; bool MenuNoOptimization = false; } Configuration; - - /** - * \brief Loads all IWDs that can be found in a specified search path. - * \param searchPath The search path that contains IWDs to be loaded. - */ - static void LoadIWDsInSearchPath(ISearchPath& searchPath); - - /** - * \brief Unloads all IWDs that were loaded from the specified search path. - * \param searchPath The search path that was used to load the IWDs to be unloaded. - */ - static void UnloadIWDsInSearchPath(ISearchPath& searchPath); - - /** - * \brief Creates a \c SearchPaths object containing all IWDs that are currently loaded. - * \return A \c SearchPaths object containing all IWDs that are currently loaded. - */ - static SearchPaths GetIWDSearchPaths(); }; diff --git a/src/ObjLoading/SearchPath/ISearchPath.h b/src/ObjLoading/SearchPath/ISearchPath.h index 737f9eee1..c174f2f37 100644 --- a/src/ObjLoading/SearchPath/ISearchPath.h +++ b/src/ObjLoading/SearchPath/ISearchPath.h @@ -7,6 +7,7 @@ #include #include #include +#include class SearchPathOpenFile { @@ -42,7 +43,7 @@ class ISearchPath * \brief Returns the path to the search path. * \return The path to the search path. */ - virtual std::string GetPath() = 0; + virtual const std::string& GetPath() = 0; /** * \brief Iterates through all files of the search path. diff --git a/src/ObjLoading/SearchPath/IWD.cpp b/src/ObjLoading/SearchPath/IWD.cpp new file mode 100644 index 000000000..f3c2b0b8c --- /dev/null +++ b/src/ObjLoading/SearchPath/IWD.cpp @@ -0,0 +1,337 @@ +#include "IWD.h" + +#include "ObjLoading.h" +#include "Utils/FileToZlibWrapper.h" + +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace +{ + class IwdFile final : public objbuf + { + public: + class IParent + { + public: + IParent() = default; + virtual ~IParent() = default; + IParent(const IParent& other) = default; + IParent(IParent&& other) noexcept = default; + IParent& operator=(const IParent& other) = default; + IParent& operator=(IParent&& other) noexcept = default; + + virtual void OnIwdFileClose() = 0; + }; + + IwdFile(IParent* parent, const unzFile container, const int64_t size) + : m_parent(parent), + m_open(true), + m_size(size), + m_container(container), + m_peeked(false), + m_peek_symbol(0) + { + } + + ~IwdFile() override + { + if (m_open) + { + close(); + } + } + + IwdFile(const IwdFile& other) = default; + IwdFile(IwdFile&& other) noexcept = default; + IwdFile& operator=(const IwdFile& other) = default; + IwdFile& operator=(IwdFile&& other) noexcept = default; + + _NODISCARD bool is_open() const override + { + return m_open; + } + + bool close() override + { + unzCloseCurrentFile(m_container); + m_open = false; + + m_parent->OnIwdFileClose(); + + return true; + } + + protected: + int_type underflow() override + { + if (m_peeked) + return m_peek_symbol; + + const auto result = unzReadCurrentFile(m_container, &m_peek_symbol, 1u); + + if (result >= 0) + { + m_peeked = true; + return m_peek_symbol; + } + + return EOF; + } + + int_type uflow() override + { + if (m_peeked) + { + m_peeked = false; + return m_peek_symbol; + } + + const auto result = unzReadCurrentFile(m_container, &m_peek_symbol, 1u); + return result >= 0 ? m_peek_symbol : EOF; + } + + std::streamsize xsgetn(char* ptr, std::streamsize count) override + { + if (m_peeked && count >= 1) + { + *ptr = static_cast(m_peek_symbol); + ptr++; + count--; + } + + const auto result = unzReadCurrentFile(m_container, ptr, static_cast(count)); + + return result >= 0 ? static_cast(result) : 0; + } + + pos_type seekoff(const off_type off, const std::ios_base::seekdir dir, const std::ios_base::openmode mode) override + { + const auto currentPos = unztell64(m_container); + + pos_type targetPos; + if (dir == std::ios_base::beg) + { + targetPos = off; + } + else if (dir == std::ios_base::cur) + { + targetPos = static_cast(currentPos) + off; + } + else + { + targetPos = m_size - off; + } + + return seekpos(targetPos, mode); + } + + pos_type seekpos(const pos_type pos, const std::ios_base::openmode mode) override + { + const auto currentPos = unztell64(m_container); + + if (static_cast(currentPos) < pos) + { + auto skipAmount = pos - static_cast(currentPos); + while (skipAmount > 0) + { + char temp[1024]; + const auto toRead = skipAmount > sizeof(temp) ? sizeof(temp) : static_cast(skipAmount); + unzReadCurrentFile(m_container, temp, toRead); + skipAmount -= toRead; + } + + return pos; + } + + if (currentPos == pos) + return currentPos; + + return std::streampos(-1); + } + + private: + IParent* m_parent; + bool m_open; + int64_t m_size; + unzFile m_container; + bool m_peeked; + int_type m_peek_symbol; + }; + + struct IwdEntry + { + int64_t m_size; + unz_file_pos m_file_pos; + + explicit IwdEntry(const int64_t size) + : m_size(size), + m_file_pos{} + { + } + }; + + class Iwd final : public ISearchPath, public IwdFile::IParent + { + public: + Iwd(std::string path, std::ifstream stream) + : m_path(std::move(path)), + m_stream(std::move(stream)), + m_unz_file{}, + m_has_open_file(false) + { + } + + ~Iwd() override + { + if (m_unz_file != nullptr) + { + unzClose(m_unz_file); + m_unz_file = nullptr; + } + } + + Iwd(const Iwd& other) = delete; + Iwd(Iwd&& other) noexcept = delete; + Iwd& operator=(const Iwd& other) = delete; + Iwd& operator=(Iwd&& other) noexcept = delete; + + /** + * \brief Initializes the IWD container. + * \return \c true when initialization was successful. + */ + bool Initialize() + { + auto ioFunctions = FileToZlibWrapper::CreateFunctions32ForFile(&m_stream); + m_unz_file = unzOpen2("", &ioFunctions); + + if (m_unz_file == nullptr) + { + std::cerr << std::format("Could not open IWD \"{}\"\n", m_path); + return false; + } + + auto ret = unzGoToFirstFile(m_unz_file); + while (ret == Z_OK) + { + unz_file_info64 info; + char fileNameBuffer[256]; + unzGetCurrentFileInfo64(m_unz_file, &info, fileNameBuffer, sizeof(fileNameBuffer), nullptr, 0, nullptr, 0); + + std::string fileName(fileNameBuffer); + fs::path path(fileName); + + if (path.has_filename()) + { + IwdEntry entry(static_cast(info.uncompressed_size)); + unzGetFilePos(m_unz_file, &entry.m_file_pos); + m_entry_map.emplace(std::move(fileName), entry); + } + + ret = unzGoToNextFile(m_unz_file); + } + + std::cout << std::format("Loaded IWD \"{}\" with {} entries\n", m_path, m_entry_map.size()); + + return true; + } + + SearchPathOpenFile Open(const std::string& fileName) override + { + if (m_unz_file == nullptr) + { + return SearchPathOpenFile(); + } + + auto iwdFilename = fileName; + std::ranges::replace(iwdFilename, '\\', '/'); + + const auto iwdEntry = m_entry_map.find(iwdFilename); + + if (iwdEntry != m_entry_map.end()) + { + assert(!m_has_open_file); + if (m_has_open_file) + { + std::cerr << "Trying to open new IWD file while last one was not yet closed.\n"; + return SearchPathOpenFile(); + } + + auto pos = iwdEntry->second.m_file_pos; + unzGoToFilePos(m_unz_file, &pos); + + if (unzOpenCurrentFile(m_unz_file) == UNZ_OK) + { + auto result = std::make_unique(this, m_unz_file, iwdEntry->second.m_size); + m_has_open_file = true; + return SearchPathOpenFile(std::make_unique(std::move(result)), iwdEntry->second.m_size); + } + + return SearchPathOpenFile(); + } + + return SearchPathOpenFile(); + } + + const std::string& GetPath() override + { + return m_path; + } + + void OnIwdFileClose() override + { + m_has_open_file = false; + } + + void Find(const SearchPathSearchOptions& options, const std::function& callback) override + { + if (options.m_disk_files_only) + { + return; + } + + for (const auto& [entryName, entry] : m_entry_map) + { + std::filesystem::path entryPath(entryName); + + if (!options.m_should_include_subdirectories && entryPath.has_parent_path()) + continue; + + if (options.m_filter_extensions && options.m_extension != entryPath.extension().string()) + continue; + + callback(entryName); + } + } + + private: + std::string m_path; + std::ifstream m_stream; + unzFile m_unz_file; + bool m_has_open_file; + + std::map m_entry_map; + }; +} // namespace + +namespace iwd +{ + std::unique_ptr LoadFromFile(const std::string& path) + { + std::ifstream inputStream(path, std::ios::in | std::ios::binary); + if (!inputStream.is_open()) + return {}; + + auto iwd = std::make_unique(path, std::move(inputStream)); + if (!iwd->Initialize()) + return {}; + + return iwd; + } +} // namespace iwd diff --git a/src/ObjLoading/SearchPath/IWD.h b/src/ObjLoading/SearchPath/IWD.h new file mode 100644 index 000000000..df6ddcf41 --- /dev/null +++ b/src/ObjLoading/SearchPath/IWD.h @@ -0,0 +1,11 @@ +#pragma once + +#include "SearchPath/ISearchPath.h" + +#include +#include + +namespace iwd +{ + std::unique_ptr LoadFromFile(const std::string& path); +} diff --git a/src/ObjLoading/SearchPath/SearchPathFilesystem.cpp b/src/ObjLoading/SearchPath/SearchPathFilesystem.cpp index 6d6804a69..a87f972ec 100644 --- a/src/ObjLoading/SearchPath/SearchPathFilesystem.cpp +++ b/src/ObjLoading/SearchPath/SearchPathFilesystem.cpp @@ -3,16 +3,18 @@ #include "Utils/ObjFileStream.h" #include +#include #include +#include namespace fs = std::filesystem; SearchPathFilesystem::SearchPathFilesystem(std::string path) + : m_path(std::move(path)) { - m_path = std::move(path); } -std::string SearchPathFilesystem::GetPath() +const std::string& SearchPathFilesystem::GetPath() { return m_path; } @@ -23,9 +25,7 @@ SearchPathOpenFile SearchPathFilesystem::Open(const std::string& fileName) std::ifstream file(filePath.string(), std::fstream::in | std::fstream::binary); if (file.is_open()) - { return SearchPathOpenFile(std::make_unique(std::move(file)), static_cast(file_size(filePath))); - } return SearchPathOpenFile(); } @@ -59,6 +59,6 @@ void SearchPathFilesystem::Find(const SearchPathSearchOptions& options, const st } catch (std::filesystem::filesystem_error& e) { - printf("Directory Iterator threw error when trying to find files: \"%s\"\n", e.what()); + std::cerr << std::format("Directory Iterator threw error when trying to find files: \"{}\"\n", e.what()); } } diff --git a/src/ObjLoading/SearchPath/SearchPathFilesystem.h b/src/ObjLoading/SearchPath/SearchPathFilesystem.h index c6c200aa1..e1d015cf8 100644 --- a/src/ObjLoading/SearchPath/SearchPathFilesystem.h +++ b/src/ObjLoading/SearchPath/SearchPathFilesystem.h @@ -12,6 +12,6 @@ class SearchPathFilesystem final : public ISearchPath explicit SearchPathFilesystem(std::string path); SearchPathOpenFile Open(const std::string& fileName) override; - std::string GetPath() override; + const std::string& GetPath() override; void Find(const SearchPathSearchOptions& options, const std::function& callback) override; }; diff --git a/src/ObjLoading/SearchPath/SearchPaths.cpp b/src/ObjLoading/SearchPath/SearchPaths.cpp index 58cf62a4f..80ed13007 100644 --- a/src/ObjLoading/SearchPath/SearchPaths.cpp +++ b/src/ObjLoading/SearchPath/SearchPaths.cpp @@ -18,9 +18,10 @@ SearchPathOpenFile SearchPaths::Open(const std::string& fileName) return SearchPathOpenFile(); } -std::string SearchPaths::GetPath() +const std::string& SearchPaths::GetPath() { - return "SearchPaths: " + std::to_string(m_search_paths.size()) + " entries"; + static const std::string STATIC_NAME = "SearchPaths"; + return STATIC_NAME; } void SearchPaths::Find(const SearchPathSearchOptions& options, const std::function& callback) diff --git a/src/ObjLoading/SearchPath/SearchPaths.h b/src/ObjLoading/SearchPath/SearchPaths.h index a1a06252d..36fe54e1d 100644 --- a/src/ObjLoading/SearchPath/SearchPaths.h +++ b/src/ObjLoading/SearchPath/SearchPaths.h @@ -2,6 +2,7 @@ #include "ISearchPath.h" +#include #include class SearchPaths final : public ISearchPath @@ -16,7 +17,7 @@ class SearchPaths final : public ISearchPath ~SearchPaths() override = default; SearchPathOpenFile Open(const std::string& fileName) override; - std::string GetPath() override; + const std::string& GetPath() override; void Find(const SearchPathSearchOptions& options, const std::function& callback) override; SearchPaths(const SearchPaths& other) = delete; diff --git a/src/Unlinker/Unlinker.cpp b/src/Unlinker/Unlinker.cpp index a4c5df9e6..5eae8e335 100644 --- a/src/Unlinker/Unlinker.cpp +++ b/src/Unlinker/Unlinker.cpp @@ -4,16 +4,17 @@ #include "ContentLister/ZoneDefWriter.h" #include "IObjLoader.h" #include "IObjWriter.h" -#include "ObjContainer/IWD/IWD.h" -#include "ObjLoading.h" #include "ObjWriting.h" +#include "SearchPath/IWD.h" #include "SearchPath/SearchPathFilesystem.h" #include "SearchPath/SearchPaths.h" #include "UnlinkerArgs.h" +#include "UnlinkerPaths.h" #include "Utils/ClassUtils.h" #include "Utils/ObjFileStream.h" #include "ZoneLoading.h" +#include #include #include #include @@ -37,13 +38,14 @@ class Unlinker::Impl if (!shouldContinue) return true; - if (!BuildSearchPaths()) + UnlinkerPaths paths; + if (!paths.LoadUserPaths(m_args)) return false; - if (!LoadZones()) + if (!LoadZones(paths)) return false; - const auto result = UnlinkZones(); + const auto result = UnlinkZones(paths); UnloadZones(); return result; @@ -55,106 +57,6 @@ class Unlinker::Impl return m_args.m_task != UnlinkerArgs::ProcessingTask::LIST && !m_args.m_skip_obj; } - /** - * \brief Loads a search path. - * \param searchPath The search path to load. - */ - void LoadSearchPath(ISearchPath& searchPath) const - { - if (ShouldLoadObj()) - { - if (m_args.m_verbose) - std::cout << std::format("Loading search path: \"{}\"\n", searchPath.GetPath()); - - ObjLoading::LoadIWDsInSearchPath(searchPath); - } - } - - /** - * \brief Unloads a search path. - * \param searchPath The search path to unload. - */ - void UnloadSearchPath(ISearchPath& searchPath) const - { - if (ShouldLoadObj()) - { - if (m_args.m_verbose) - std::cout << std::format("Unloading search path: \"{}\"\n", searchPath.GetPath()); - - ObjLoading::UnloadIWDsInSearchPath(searchPath); - } - } - - /** - * \brief Loads all search paths that are valid for the specified zone and returns them. - * \param zonePath The path to the zone file that should be prepared for. - * \return A \c SearchPaths object that contains all search paths that should be considered when loading the specified zone. - */ - SearchPaths GetSearchPathsForZone(const std::string& zonePath) - { - SearchPaths searchPathsForZone; - const auto absoluteZoneDirectory = fs::absolute(std::filesystem::path(zonePath).remove_filename()).string(); - - if (m_last_zone_search_path != nullptr && m_last_zone_search_path->GetPath() == absoluteZoneDirectory) - { - searchPathsForZone.IncludeSearchPath(m_last_zone_search_path.get()); - } - else if (m_absolute_search_paths.find(absoluteZoneDirectory) == m_absolute_search_paths.end()) - { - if (m_last_zone_search_path) - { - UnloadSearchPath(*m_last_zone_search_path); - } - - m_last_zone_search_path = std::make_unique(absoluteZoneDirectory); - searchPathsForZone.IncludeSearchPath(m_last_zone_search_path.get()); - LoadSearchPath(*m_last_zone_search_path); - } - - for (auto* iwd : IWD::Repository) - { - searchPathsForZone.IncludeSearchPath(iwd); - } - - return searchPathsForZone; - } - - /** - * \brief Initializes the Unlinker object's search paths based on the user's input. - * \return \c true if building the search paths was successful, otherwise \c false. - */ - bool BuildSearchPaths() - { - for (const auto& path : m_args.m_user_search_paths) - { - auto absolutePath = fs::absolute(path); - - if (!fs::is_directory(absolutePath)) - { - std::cerr << std::format("Could not find directory of search path: \"{}\"\n", path); - return false; - } - - auto searchPath = std::make_unique(absolutePath.string()); - LoadSearchPath(*searchPath); - m_search_paths.CommitSearchPath(std::move(searchPath)); - - m_absolute_search_paths.insert(absolutePath.string()); - } - - if (m_args.m_verbose) - { - std::cout << std::format("{} SearchPaths{}\n", m_absolute_search_paths.size(), !m_absolute_search_paths.empty() ? ":" : ""); - for (const auto& absoluteSearchPath : m_absolute_search_paths) - std::cout << std::format(" \"{}\"\n", absoluteSearchPath); - - if (!m_absolute_search_paths.empty()) - std::cerr << "\n"; - } - - return true; - } - bool WriteZoneDefinitionFile(const Zone& zone, const fs::path& zoneDefinitionFileFolder) const { auto zoneDefinitionFilePath(zoneDefinitionFileFolder); @@ -251,7 +153,7 @@ class Unlinker::Impl * \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. + * \return \c true if handling the zone was successful, otherwise \c false */ bool HandleZone(ISearchPath& searchPath, Zone& zone) const { @@ -310,7 +212,7 @@ class Unlinker::Impl return true; } - bool LoadZones() + bool LoadZones(UnlinkerPaths& paths) { for (const auto& zonePath : m_args.m_zones_to_load) { @@ -322,9 +224,7 @@ class Unlinker::Impl auto absoluteZoneDirectory = absolute(std::filesystem::path(zonePath).remove_filename()).string(); - auto searchPathsForZone = GetSearchPathsForZone(absoluteZoneDirectory); - searchPathsForZone.IncludeSearchPath(&m_search_paths); - + auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory); auto zone = ZoneLoading::LoadZone(zonePath); if (zone == nullptr) { @@ -338,7 +238,7 @@ class Unlinker::Impl if (ShouldLoadObj()) { const auto* objLoader = IObjLoader::GetObjLoaderForGame(zone->m_game->GetId()); - objLoader->LoadReferencedContainersForZone(searchPathsForZone, *zone); + objLoader->LoadReferencedContainersForZone(*searchPathsForZone, *zone); } m_loaded_zones.emplace_back(std::move(zone)); @@ -370,7 +270,7 @@ class Unlinker::Impl m_loaded_zones.clear(); } - bool UnlinkZones() + bool UnlinkZones(UnlinkerPaths& paths) const { for (const auto& zonePath : m_args.m_zones_to_unlink) { @@ -385,8 +285,7 @@ class Unlinker::Impl zoneDirectory = fs::current_path(); auto absoluteZoneDirectory = absolute(zoneDirectory).string(); - auto searchPathsForZone = GetSearchPathsForZone(absoluteZoneDirectory); - searchPathsForZone.IncludeSearchPath(&m_search_paths); + auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory); std::string zoneName; auto zone = ZoneLoading::LoadZone(zonePath); @@ -402,9 +301,9 @@ class Unlinker::Impl const auto* objLoader = IObjLoader::GetObjLoaderForGame(zone->m_game->GetId()); if (ShouldLoadObj()) - objLoader->LoadReferencedContainersForZone(searchPathsForZone, *zone); + objLoader->LoadReferencedContainersForZone(*searchPathsForZone, *zone); - if (!HandleZone(searchPathsForZone, *zone)) + if (!HandleZone(*searchPathsForZone, *zone)) return false; if (ShouldLoadObj()) @@ -419,10 +318,6 @@ class Unlinker::Impl } UnlinkerArgs m_args; - SearchPaths m_search_paths; - std::unique_ptr m_last_zone_search_path; - std::set m_absolute_search_paths; - std::vector> m_loaded_zones; }; diff --git a/src/Unlinker/UnlinkerArgs.h b/src/Unlinker/UnlinkerArgs.h index f9e61de20..0e07dd2bf 100644 --- a/src/Unlinker/UnlinkerArgs.h +++ b/src/Unlinker/UnlinkerArgs.h @@ -1,7 +1,9 @@ #pragma once + #include "Utils/Arguments/ArgumentParser.h" #include "Zone/Zone.h" +#include #include #include #include @@ -37,7 +39,7 @@ class UnlinkerArgs LIST }; - enum class AssetTypeHandling + enum class AssetTypeHandling : std::uint8_t { EXCLUDE, INCLUDE diff --git a/src/Unlinker/UnlinkerPaths.cpp b/src/Unlinker/UnlinkerPaths.cpp new file mode 100644 index 000000000..506c2ab8b --- /dev/null +++ b/src/Unlinker/UnlinkerPaths.cpp @@ -0,0 +1,72 @@ +#include "UnlinkerPaths.h" + +#include "SearchPath/IWD.h" +#include "SearchPath/SearchPathFilesystem.h" +#include "Utils/StringUtils.h" + +#include +#include + +namespace fs = std::filesystem; + +bool UnlinkerPaths::LoadUserPaths(const UnlinkerArgs& args) +{ + for (const auto& path : args.m_user_search_paths) + { + auto absolutePath = fs::absolute(path); + + if (!fs::is_directory(absolutePath)) + { + std::cerr << std::format("Could not find directory of search path: \"{}\"\n", path); + return false; + } + + auto searchPathName = absolutePath.string(); + m_user_paths.CommitSearchPath(std::make_unique(searchPathName)); + m_specified_user_paths.emplace(std::move(searchPathName)); + } + + std::cout << std::format("{} SearchPaths{}\n", m_specified_user_paths.size(), !m_specified_user_paths.empty() ? ":" : ""); + for (const auto& absoluteSearchPath : m_specified_user_paths) + std::cout << std::format(" \"{}\"\n", absoluteSearchPath); + + if (!m_specified_user_paths.empty()) + std::cerr << "\n"; + + return true; +} + +std::unique_ptr UnlinkerPaths::GetSearchPathsForZone(const std::string& zonePath) +{ + const auto absoluteZoneDirectory = fs::absolute(std::filesystem::path(zonePath).remove_filename()); + const auto absoluteZoneDirectoryString = absoluteZoneDirectory.string(); + if (m_last_zone_path != absoluteZoneDirectoryString) + { + m_last_zone_path = absoluteZoneDirectoryString; + m_last_zone_search_paths = SearchPaths(); + m_last_zone_search_paths.CommitSearchPath(std::make_unique(absoluteZoneDirectoryString)); + + std::filesystem::directory_iterator iterator(absoluteZoneDirectory); + const auto end = fs::end(iterator); + for (auto i = fs::begin(iterator); i != end; ++i) + { + if (!i->is_regular_file()) + continue; + + auto extension = i->path().extension().string(); + utils::MakeStringLowerCase(extension); + if (extension == ".iwd") + { + auto iwd = iwd::LoadFromFile(i->path().string()); + if (iwd) + m_last_zone_search_paths.CommitSearchPath(std::move(iwd)); + } + } + } + + auto result = std::make_unique(); + result->IncludeSearchPath(&m_last_zone_search_paths); + result->IncludeSearchPath(&m_user_paths); + + return result; +} diff --git a/src/Unlinker/UnlinkerPaths.h b/src/Unlinker/UnlinkerPaths.h new file mode 100644 index 000000000..7396f5238 --- /dev/null +++ b/src/Unlinker/UnlinkerPaths.h @@ -0,0 +1,21 @@ +#pragma once + +#include "SearchPath/SearchPaths.h" +#include "UnlinkerArgs.h" + +#include +#include + +class UnlinkerPaths +{ +public: + bool LoadUserPaths(const UnlinkerArgs& args); + std::unique_ptr GetSearchPathsForZone(const std::string& zonePath); + +private: + std::string m_last_zone_path; + SearchPaths m_last_zone_search_paths; + + std::unordered_set m_specified_user_paths; + SearchPaths m_user_paths; +}; diff --git a/test/ObjLoadingTests/Mock/MockSearchPath.cpp b/test/ObjLoadingTests/Mock/MockSearchPath.cpp index 3bf0c92d7..8171eff82 100644 --- a/test/ObjLoadingTests/Mock/MockSearchPath.cpp +++ b/test/ObjLoadingTests/Mock/MockSearchPath.cpp @@ -17,9 +17,10 @@ SearchPathOpenFile MockSearchPath::Open(const std::string& fileName) return {std::make_unique(foundFileData->second), foundFileData->second.size()}; } -std::string MockSearchPath::GetPath() +const std::string& MockSearchPath::GetPath() { - return "MockFiles"; + const static std::string NAME = "MockFiles"; + return NAME; } void MockSearchPath::Find(const SearchPathSearchOptions& options, const std::function& callback) {} diff --git a/test/ObjLoadingTests/Mock/MockSearchPath.h b/test/ObjLoadingTests/Mock/MockSearchPath.h index 6951326e6..4fb877fc3 100644 --- a/test/ObjLoadingTests/Mock/MockSearchPath.h +++ b/test/ObjLoadingTests/Mock/MockSearchPath.h @@ -11,6 +11,6 @@ class MockSearchPath final : public ISearchPath void AddFileData(std::string fileName, std::string fileData); SearchPathOpenFile Open(const std::string& fileName) override; - std::string GetPath() override; + const std::string& GetPath() override; void Find(const SearchPathSearchOptions& options, const std::function& callback) override; };