From 153f8f2e89260aa666286b591909fd870169d1b8 Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 30 Dec 2019 23:52:33 +0100 Subject: [PATCH] Unlinker/ObjLoading: Add skeleton for dynamically loading search paths based on current zone --- src/ObjLoading/Game/T6/ObjLoaderT6.cpp | 7 +- src/ObjLoading/Game/T6/ObjLoaderT6.h | 6 +- src/ObjLoading/IObjLoader.h | 5 +- src/ObjLoading/ObjContainer/IWD/IWD.cpp | 32 ++ src/ObjLoading/ObjContainer/IWD/IWD.h | 17 + src/ObjLoading/ObjLoading.cpp | 8 +- src/ObjLoading/ObjLoading.h | 6 +- src/ObjLoading/SearchPath/ISearchPath.h | 32 ++ .../SearchPath/SearchPathFilesystem.cpp | 79 ++++ .../SearchPath/SearchPathFilesystem.h | 19 + src/ObjLoading/SearchPath/SearchPaths.cpp | 55 ++- src/ObjLoading/SearchPath/SearchPaths.h | 24 +- src/Unlinker/Unlinker.cpp | 430 ++++++++++++++++++ src/Unlinker/Unlinker.h | 24 + src/Unlinker/main.cpp | 161 +------ 15 files changed, 727 insertions(+), 178 deletions(-) create mode 100644 src/Unlinker/Unlinker.cpp create mode 100644 src/Unlinker/Unlinker.h diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index 69bb71bf..218c8d49 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -27,13 +27,13 @@ bool ObjLoaderT6::SupportsZone(Zone* zone) return zone->m_game == &g_GameT6; } -void ObjLoaderT6::LoadIPakForZone(std::string ipakName, Zone* zone) +void ObjLoaderT6::LoadIPakForZone(const std::string& ipakName, Zone* zone) { printf("Loading ipak '%s' for zone '%s'\n", ipakName.c_str(), zone->m_name.c_str()); // TODO } -void ObjLoaderT6::LoadReferencedContainersForZone(Zone* zone) +void ObjLoaderT6::LoadReferencedContainersForZone(ISearchPath* searchPath, Zone* zone) { auto* assetPoolT6 = dynamic_cast(zone->GetPools()); const int zoneNameHash = Com_HashKey(zone->m_name.c_str(), 64); @@ -55,6 +55,7 @@ void ObjLoaderT6::LoadReferencedContainersForZone(Zone* zone) } } -void ObjLoaderT6::LoadObjDataForZone(Zone* zone) +void ObjLoaderT6::LoadObjDataForZone(ISearchPath* searchPath, Zone* zone) { + // TODO } diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.h b/src/ObjLoading/Game/T6/ObjLoaderT6.h index cf956f3a..dac1af4f 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.h +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.h @@ -8,10 +8,10 @@ class ObjLoaderT6 final : public IObjLoader static const int GLOBAL_HASH; static int Com_HashKey(const char* str, int maxLen); - static void LoadIPakForZone(std::string ipakName, Zone* zone); + static void LoadIPakForZone(const std::string& ipakName, Zone* zone); public: bool SupportsZone(Zone* zone) override; - void LoadReferencedContainersForZone(Zone* zone) override; - void LoadObjDataForZone(Zone* zone) override; + void LoadReferencedContainersForZone(ISearchPath* searchPath, Zone* zone) override; + void LoadObjDataForZone(ISearchPath* searchPath, Zone* zone) override; }; diff --git a/src/ObjLoading/IObjLoader.h b/src/ObjLoading/IObjLoader.h index 994a6d7e..3a87267b 100644 --- a/src/ObjLoading/IObjLoader.h +++ b/src/ObjLoading/IObjLoader.h @@ -1,5 +1,6 @@ #pragma once +#include "SearchPath/ISearchPath.h" #include "Zone/Zone.h" class IObjLoader @@ -8,6 +9,6 @@ public: virtual ~IObjLoader() = default; virtual bool SupportsZone(Zone* zone) = 0; - virtual void LoadReferencedContainersForZone(Zone* zone) = 0; - virtual void LoadObjDataForZone(Zone* zone) = 0; + virtual void LoadReferencedContainersForZone(ISearchPath* searchPath, Zone* zone) = 0; + virtual void LoadObjDataForZone(ISearchPath* searchPath, Zone* zone) = 0; }; \ No newline at end of file diff --git a/src/ObjLoading/ObjContainer/IWD/IWD.cpp b/src/ObjLoading/ObjContainer/IWD/IWD.cpp index e69de29b..c07f2a42 100644 --- a/src/ObjLoading/ObjContainer/IWD/IWD.cpp +++ b/src/ObjLoading/ObjContainer/IWD/IWD.cpp @@ -0,0 +1,32 @@ +#include "IWD.h" + +IWD::IWD(std::string path) +{ + m_path = std::move(path); +} + +FileAPI::IFile* IWD::Open(const std::string& fileName) +{ + // TODO + return nullptr; +} + +void IWD::FindAll(std::function callback) +{ + // TODO +} + +void IWD::FindAllOnDisk(std::function callback) +{ + // Files inside an IWD are not on the disk's file system directly. Therefore do nothing here. +} + +void IWD::FindByExtension(const std::string& extension, std::function callback) +{ + // TODO +} + +void IWD::FindOnDiskByExtension(const std::string& extension, std::function callback) +{ + // Files inside an IWD are not on the disk's file system directly. Therefore do nothing here. +} diff --git a/src/ObjLoading/ObjContainer/IWD/IWD.h b/src/ObjLoading/ObjContainer/IWD/IWD.h index e69de29b..257f5e1f 100644 --- a/src/ObjLoading/ObjContainer/IWD/IWD.h +++ b/src/ObjLoading/ObjContainer/IWD/IWD.h @@ -0,0 +1,17 @@ +#pragma once + +#include "SearchPath/ISearchPath.h" + +class IWD final : public ISearchPath +{ + std::string m_path; + +public: + explicit IWD(std::string path); + + FileAPI::IFile* Open(const std::string& fileName) override; + void FindAll(std::function callback) override; + void FindAllOnDisk(std::function callback) override; + void FindByExtension(const std::string& extension, std::function callback) override; + void FindOnDiskByExtension(const std::string& extension, std::function callback) override; +}; \ No newline at end of file diff --git a/src/ObjLoading/ObjLoading.cpp b/src/ObjLoading/ObjLoading.cpp index 95599c38..bd8e71eb 100644 --- a/src/ObjLoading/ObjLoading.cpp +++ b/src/ObjLoading/ObjLoading.cpp @@ -8,25 +8,25 @@ IObjLoader* objLoaders[] new ObjLoaderT6() }; -void ObjLoading::LoadReferencedContainersForZone(Zone* zone) +void ObjLoading::LoadReferencedContainersForZone(ISearchPath* searchPath, Zone* zone) { for (auto* loader : objLoaders) { if (loader->SupportsZone(zone)) { - loader->LoadReferencedContainersForZone(zone); + loader->LoadReferencedContainersForZone(searchPath, zone); return; } } } -void ObjLoading::LoadObjDataForZone(Zone* zone) +void ObjLoading::LoadObjDataForZone(ISearchPath* searchPath, Zone* zone) { for (auto* loader : objLoaders) { if (loader->SupportsZone(zone)) { - loader->LoadObjDataForZone(zone); + loader->LoadObjDataForZone(searchPath, zone); return; } } diff --git a/src/ObjLoading/ObjLoading.h b/src/ObjLoading/ObjLoading.h index 50dfd6d0..6f183546 100644 --- a/src/ObjLoading/ObjLoading.h +++ b/src/ObjLoading/ObjLoading.h @@ -1,11 +1,13 @@ #pragma once #include "Zone/Zone.h" +#include "SearchPath/ISearchPath.h" class ObjLoading { public: - static void LoadReferencedContainersForZone(Zone* zone); - static void LoadObjDataForZone(Zone* zone); + static void LoadReferencedContainersForZone(ISearchPath* searchPath, Zone* zone); static void UnloadContainersOfZone(Zone* zone); + + static void LoadObjDataForZone(ISearchPath* searchPath, Zone* zone); }; \ No newline at end of file diff --git a/src/ObjLoading/SearchPath/ISearchPath.h b/src/ObjLoading/SearchPath/ISearchPath.h index 3def0689..f5308a55 100644 --- a/src/ObjLoading/SearchPath/ISearchPath.h +++ b/src/ObjLoading/SearchPath/ISearchPath.h @@ -2,11 +2,43 @@ #include "Utils/FileAPI.h" #include +#include class ISearchPath { public: virtual ~ISearchPath() = default; + /** + * \brief Opens a file relative to the search path. + * \param fileName The relative path to the file to open. + * \return A pointer to an \c IFile object to read the found file or \c nullptr when no file could be found. + */ virtual FileAPI::IFile* Open(const std::string& fileName) = 0; + + /** + * \brief Iterates through all files of the search path. + * \param callback The callback to call for each found file with it's path relative to the search path. + */ + virtual void FindAll(std::function callback) = 0; + + /** + * \brief Iterates through all files available through the OS file system. + * \param callback The callback to call for each found file with it's full path. + */ + virtual void FindAllOnDisk(std::function callback) = 0; + + /** + * \brief Iterates through all files of the search path with the specified extension. + * \param extension The extension of all files to find. + * \param callback The callback to call for each found file with it's path relative to the search path. + */ + virtual void FindByExtension(const std::string& extension, std::function callback) = 0; + + /** + * \brief Iterates through all files available through the OS file system with the specified extension. + * \param extension The extension of all files to find. + * \param callback The callback to call for each found file with it's full path. + */ + virtual void FindOnDiskByExtension(const std::string& extension, std::function callback) = 0; }; diff --git a/src/ObjLoading/SearchPath/SearchPathFilesystem.cpp b/src/ObjLoading/SearchPath/SearchPathFilesystem.cpp index e69de29b..1282b3f7 100644 --- a/src/ObjLoading/SearchPath/SearchPathFilesystem.cpp +++ b/src/ObjLoading/SearchPath/SearchPathFilesystem.cpp @@ -0,0 +1,79 @@ +#include "SearchPathFilesystem.h" +#include "Utils/PathUtils.h" + +#include + +SearchPathFilesystem::SearchPathFilesystem(std::string path) +{ + m_path = std::move(path); +} + +const std::string& SearchPathFilesystem::GetPath() const +{ + return m_path; +} + +FileAPI::IFile* SearchPathFilesystem::Open(const std::string& fileName) +{ + FileAPI::File file = FileAPI::Open(utils::Path::Combine(m_path, fileName), FileAPI::Mode::MODE_READ); + + if (file.IsOpen()) + { + return new FileAPI::File(std::move(file)); + } + + return nullptr; +} + + +void SearchPathFilesystem::FindAll(const std::function callback) +{ + std::filesystem::recursive_directory_iterator iterator(m_path); + + for (const auto entry = begin(iterator); iterator != end(iterator); ++iterator) + { + callback(entry->path().string()); + } +} + +void SearchPathFilesystem::FindAllOnDisk(const std::function callback) +{ + std::filesystem::recursive_directory_iterator iterator(m_path); + + for (const auto entry = begin(iterator); iterator != end(iterator); ++iterator) + { + callback(absolute(entry->path()).string()); + } +} + +void SearchPathFilesystem::FindByExtension(const std::string& extension, + const std::function callback) +{ + std::filesystem::recursive_directory_iterator iterator(m_path); + + for (const auto entry = begin(iterator); iterator != end(iterator); ++iterator) + { + auto entryPath = entry->path(); + + if (entryPath.extension().string() == extension) + { + callback(entryPath.string()); + } + } +} + +void SearchPathFilesystem::FindOnDiskByExtension(const std::string& extension, + const std::function callback) +{ + std::filesystem::recursive_directory_iterator iterator(m_path); + + for (const auto entry = begin(iterator); iterator != end(iterator); ++iterator) + { + auto entryPath = entry->path(); + + if (entryPath.extension().string() == extension) + { + callback(absolute(entryPath).string()); + } + } +} diff --git a/src/ObjLoading/SearchPath/SearchPathFilesystem.h b/src/ObjLoading/SearchPath/SearchPathFilesystem.h index e69de29b..2d605650 100644 --- a/src/ObjLoading/SearchPath/SearchPathFilesystem.h +++ b/src/ObjLoading/SearchPath/SearchPathFilesystem.h @@ -0,0 +1,19 @@ +#pragma once + +#include "ISearchPath.h" +#include + +class SearchPathFilesystem final : public ISearchPath +{ + std::string m_path; + +public: + explicit SearchPathFilesystem(std::string path); + const std::string& GetPath() const; + + FileAPI::IFile* Open(const std::string& fileName) override; + void FindAll(std::function callback) override; + void FindAllOnDisk(std::function callback) override; + void FindByExtension(const std::string& extension, std::function callback) override; + void FindOnDiskByExtension(const std::string& extension, std::function callback) override; +}; \ No newline at end of file diff --git a/src/ObjLoading/SearchPath/SearchPaths.cpp b/src/ObjLoading/SearchPath/SearchPaths.cpp index 6f8796f7..3a2ea305 100644 --- a/src/ObjLoading/SearchPath/SearchPaths.cpp +++ b/src/ObjLoading/SearchPath/SearchPaths.cpp @@ -1,11 +1,17 @@ #include "SearchPaths.h" +#include + +SearchPaths::SearchPaths() = default; + SearchPaths::~SearchPaths() { - for(auto searchPath : m_search_paths) + for(auto searchPathToFree : m_to_free) { - delete searchPath; + delete searchPathToFree; } + m_to_free.clear(); + m_search_paths.clear(); } @@ -37,9 +43,9 @@ SearchPaths& SearchPaths::operator=(SearchPaths&& other) noexcept FileAPI::IFile* SearchPaths::Open(const std::string& fileName) { - for(auto searchPath : m_search_paths) + for(auto searchPathEntry : m_search_paths) { - auto* file = searchPath->Open(fileName); + auto* file = searchPathEntry->Open(fileName); if(file != nullptr) { @@ -50,7 +56,46 @@ FileAPI::IFile* SearchPaths::Open(const std::string& fileName) return nullptr; } -void SearchPaths::AddSearchPath(ISearchPath* searchPath) + +void SearchPaths::FindAll(const std::function callback) +{ + for (auto searchPathEntry : m_search_paths) + { + searchPathEntry->FindAll(callback); + } +} + +void SearchPaths::FindAllOnDisk(const std::function callback) +{ + for (auto searchPathEntry : m_search_paths) + { + searchPathEntry->FindAllOnDisk(callback); + } +} + +void SearchPaths::FindByExtension(const std::string& extension, const std::function callback) +{ + for (auto searchPathEntry : m_search_paths) + { + searchPathEntry->FindByExtension(extension, callback); + } +} + +void SearchPaths::FindOnDiskByExtension(const std::string& extension, const std::function callback) +{ + for (auto searchPathEntry : m_search_paths) + { + searchPathEntry->FindOnDiskByExtension(extension, callback); + } +} + +void SearchPaths::CommitSearchPath(ISearchPath* searchPath) +{ + m_search_paths.push_back(searchPath); + m_to_free.push_back(searchPath); +} + +void SearchPaths::IncludeSearchPath(ISearchPath* searchPath) { m_search_paths.push_back(searchPath); } diff --git a/src/ObjLoading/SearchPath/SearchPaths.h b/src/ObjLoading/SearchPath/SearchPaths.h index 44e45eb4..844427e6 100644 --- a/src/ObjLoading/SearchPath/SearchPaths.h +++ b/src/ObjLoading/SearchPath/SearchPaths.h @@ -6,19 +6,41 @@ class SearchPaths final : public ISearchPath { std::vector m_search_paths; + std::vector m_to_free; public: using iterator = std::vector::iterator; + SearchPaths(); ~SearchPaths() override; + FileAPI::IFile* Open(const std::string& fileName) override; + void FindAll(std::function callback) override; + void FindAllOnDisk(std::function callback) override; + void FindByExtension(const std::string& extension, std::function callback) override; + void FindOnDiskByExtension(const std::string& extension, std::function callback) override; SearchPaths(const SearchPaths& other); SearchPaths(SearchPaths&& other) noexcept; SearchPaths& operator=(const SearchPaths& other); SearchPaths& operator=(SearchPaths&& other) noexcept; - void AddSearchPath(ISearchPath* searchPath); + /** + * \brief Adds a search path that gets deleted upon destruction of the \c SearchPaths object. + * \param searchPath The search path to add. + */ + void CommitSearchPath(ISearchPath* searchPath); + + /** + * \brief Adds a search path that does \b NOT get deleted upon destruction of the \c SearchPaths object. + * \param searchPath The search path to add. + */ + void IncludeSearchPath(ISearchPath* searchPath); + + /** + * \brief Removes a search path from the \c SearchPaths object. If the search path was committed then it will \b NOT be deleted when destructing the \c SearchPaths object. + * \param searchPath The search path to remove. + */ void RemoveSearchPath(ISearchPath* searchPath); iterator begin(); diff --git a/src/Unlinker/Unlinker.cpp b/src/Unlinker/Unlinker.cpp new file mode 100644 index 00000000..8c6b043b --- /dev/null +++ b/src/Unlinker/Unlinker.cpp @@ -0,0 +1,430 @@ +#include "Unlinker.h" + +#include "Utils/Arguments/ArgumentParser.h" +#include "Utils/Arguments/UsageInformation.h" +#include "ZoneLoading.h" +#include "ObjWriting.h" +#include "ContentPrinter.h" +#include "Utils/PathUtils.h" +#include "Utils/FileAPI.h" +#include "ObjLoading.h" +#include "SearchPath/SearchPaths.h" +#include "SearchPath/SearchPathFilesystem.h" + +#include +#include +#include + +const CommandLineOption* optionHelp = CommandLineOption::Builder::Create() + .WithShortName("?") + .WithLongName("help") + .WithDescription("Displays usage information.") + .Build(); + +const CommandLineOption* optionVerbose = CommandLineOption::Builder::Create() + .WithShortName("v") + .WithLongName("verbose") + .WithDescription("Outputs a lot more and more detailed messages.") + .Build(); + +const CommandLineOption* optionMinimalZoneFile = CommandLineOption::Builder::Create() + .WithShortName("min") + .WithLongName("minimal-zone") + .WithDescription( + "Minimizes the size of the zone file output by only including assets that are not a dependency of another asset.") + .Build(); + +const CommandLineOption* optionList = CommandLineOption::Builder::Create() + .WithShortName("l") + .WithLongName("list") + .WithDescription( + "Lists the contents of a zone instead of writing them to the disk.") + .Build(); + +const CommandLineOption* optionOutputFolder = CommandLineOption::Builder::Create() + .WithShortName("o") + .WithLongName("output-folder") + .WithDescription( + "Specifies the output folder containing the contents of the unlinked zones. Defaults to ./%zoneName%") + .WithParameter("outputFolderPath") + .Build(); + +const CommandLineOption* optionSearchPath = CommandLineOption::Builder::Create() + .WithLongName("search-path") + .WithDescription( + "Specifies a semi-colon separated list of paths to search for additional game files.") + .WithParameter("searchPathString") + .Build(); + +const CommandLineOption* commandLineOptions[] +{ + optionHelp, + optionVerbose, + optionMinimalZoneFile, + optionList, + optionOutputFolder, + optionSearchPath +}; + +class Unlinker::Impl +{ + SearchPaths m_search_paths; + SearchPathFilesystem* m_last_zone_search_path; + std::set m_absolute_search_paths; + + ArgumentParser m_argument_parser; + bool m_verbose; + bool m_should_load_obj; + + /** + * \brief Loads a search path. + * \param searchPath The search path to load. + */ + void LoadSearchPath(SearchPathFilesystem* searchPath) + { + if(m_should_load_obj) + { + if(m_verbose) + { + printf("Loading search path: \"%s\"\n", searchPath->GetPath().c_str()); + } + } + } + + /** + * \brief Unloads a search path. + * \param searchPath The search path to unload. + */ + void UnloadSearchPath(SearchPathFilesystem* searchPath) + { + if(m_should_load_obj) + { + if(m_verbose) + { + printf("Unloading search path: \"%s\"\n", searchPath->GetPath().c_str()); + } + } + } + + /** + * \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 std::string absoluteZoneDirectory = 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); + } + else if (m_absolute_search_paths.find(absoluteZoneDirectory) == m_absolute_search_paths.end()) + { + if (m_last_zone_search_path != nullptr) + { + UnloadSearchPath(m_last_zone_search_path); + delete m_last_zone_search_path; + } + + m_last_zone_search_path = new SearchPathFilesystem(absoluteZoneDirectory); + searchPathsForZone.IncludeSearchPath(m_last_zone_search_path); + LoadSearchPath(m_last_zone_search_path); + } + + return searchPathsForZone; + } + + /** + * \brief Splits a path string as user input into a list of paths. + * \param pathsString The path string that was taken as user input. + * \param output A set for strings to save the output to. + * \return \c true if the user input was valid and could be processed successfully, otherwise \c false. + */ + static bool ParsePathsString(const std::string& pathsString, std::set& output) + { + std::ostringstream currentPath; + bool pathStart = true; + int quotationPos = 0; // 0 = before quotations, 1 = in quotations, 2 = after quotations + + for (auto character : pathsString) + { + switch (character) + { + case '"': + if (quotationPos == 0 && pathStart) + { + quotationPos = 1; + } + else if (quotationPos == 1) + { + quotationPos = 2; + pathStart = false; + } + else + { + return false; + } + break; + + case ';': + if (quotationPos != 1) + { + std::string path = currentPath.str(); + if (!path.empty()) + { + output.insert(path); + currentPath = std::ostringstream(); + } + + pathStart = true; + quotationPos = 0; + } + else + { + currentPath << character; + } + break; + + default: + currentPath << character; + pathStart = false; + break; + } + } + + if (currentPath.tellp() > 0) + { + output.insert(currentPath.str()); + } + + return true; + } + + /** + * \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() + { + if (m_argument_parser.IsOptionSpecified(optionSearchPath)) + { + std::set pathList; + if (!ParsePathsString(m_argument_parser.GetValueForOption(optionSearchPath), pathList)) + { + return false; + } + + for (const auto& path : pathList) + { + std::string absolutePath = std::filesystem::absolute(path).string(); + + SearchPathFilesystem* searchPath = new SearchPathFilesystem(absolutePath); + LoadSearchPath(searchPath); + m_search_paths.CommitSearchPath(searchPath); + + m_absolute_search_paths.insert(absolutePath); + } + } + + if (m_verbose) + { + printf("%u SearchPaths%s\n", m_absolute_search_paths.size(), !m_absolute_search_paths.empty() ? ":" : ""); + for (const auto& absoluteSearchPath : m_absolute_search_paths) + { + printf(" \"%s\"\n", absoluteSearchPath.c_str()); + } + + if (!m_absolute_search_paths.empty()) + { + puts(""); + } + } + + return true; + } + + /** + * \brief Prints a command line usage help text for the Unlinker tool to stdout. + */ + static void PrintUsage() + { + UsageInformation usage("unlinker.exe"); + + for (auto commandLineOption : commandLineOptions) + { + usage.AddCommandLineOption(commandLineOption); + } + + usage.AddArgument("pathToZone"); + usage.SetVariableArguments(true); + + usage.Print(); + } + + /** + * \brief Converts the output path specified by command line arguments to a path applies for the specified zone. + * \param path The path that was specified as user input. + * \param zone The zone to resolve the path input for. + * \return An output path for the zone based on the user input. + */ + std::string ResolveOutputFolderPath(const std::string& path, Zone* zone) const + { + return std::regex_replace(path, std::regex("%zoneName%"), zone->m_name); + } + + /** + * \brief Performs the tasks specified by the command line arguments on the specified zone. + * \param zone The zone to handle. + * \return \c true if handling the zone was successful, otherwise \c false. + */ + bool HandleZone(Zone* zone) + { + if (m_argument_parser.IsOptionSpecified(optionList)) + { + const ContentPrinter printer(zone); + printer.PrintContent(); + } + else + { + const bool minimalisticZoneDefinition = m_argument_parser.IsOptionSpecified(optionMinimalZoneFile); + + std::string outputFolderPath; + if (m_argument_parser.IsOptionSpecified(optionOutputFolder)) + { + outputFolderPath = ResolveOutputFolderPath(m_argument_parser.GetValueForOption(optionOutputFolder), + zone); + } + else + { + outputFolderPath = ResolveOutputFolderPath("./%zoneName%", zone); + } + + FileAPI::DirectoryCreate(outputFolderPath); + + const std::string zoneDefinitionFileFolder = utils::Path::Combine(outputFolderPath, "zone_source"); + FileAPI::DirectoryCreate(zoneDefinitionFileFolder); + + FileAPI::File zoneDefinitionFile = FileAPI::Open( + utils::Path::Combine(zoneDefinitionFileFolder, zone->m_name + ".zone"), + FileAPI::Mode::MODE_WRITE); + + if (zoneDefinitionFile.IsOpen()) + { + ObjWriting::WriteZoneDefinition(zone, &zoneDefinitionFile, minimalisticZoneDefinition); + ObjWriting::DumpZone(zone, outputFolderPath); + } + else + { + printf("Failed to open file for zone definition file of zone \"%s\".\n", zone->m_name.c_str()); + return false; + } + + zoneDefinitionFile.Close(); + } + + return true; + } + +public: + Impl() + : m_argument_parser(commandLineOptions, _countof(commandLineOptions)) + { + m_last_zone_search_path = nullptr; + m_verbose = false; + m_should_load_obj = false; + } + + /** + * \copydoc Unlinker::Start + */ + bool Start(const int argc, const char** argv) + { + if (!m_argument_parser.ParseArguments(argc, argv)) + { + PrintUsage(); + return false; + } + + m_verbose = m_argument_parser.IsOptionSpecified(optionVerbose); + m_should_load_obj = !m_argument_parser.IsOptionSpecified(optionList); + + // Check if the user requested help + if (m_argument_parser.IsOptionSpecified(optionHelp)) + { + PrintUsage(); + return true; + } + + const std::vector arguments = m_argument_parser.GetArguments(); + const size_t argCount = arguments.size(); + if (argCount <= 1) + { + // No zones to load specified... + PrintUsage(); + return false; + } + + if (!BuildSearchPaths()) + { + return false; + } + + for (unsigned argIndex = 1; argIndex < argCount; argIndex++) + { + const std::string& zonePath = arguments[argIndex]; + std::string absoluteZoneDirectory = absolute(std::filesystem::path(zonePath).remove_filename()).string(); + + SearchPaths searchPathsForZone = GetSearchPathsForZone(absoluteZoneDirectory); + + Zone* zone = ZoneLoading::LoadZone(zonePath); + if (zone == nullptr) + { + printf("Failed to load zone \"%s\".\n", zonePath.c_str()); + return false; + } + + if(m_verbose) + { + printf("Loaded zone \"%s\"\n", zone->m_name.c_str()); + } + + if (m_should_load_obj) + { + ObjLoading::LoadReferencedContainersForZone(&searchPathsForZone, zone); + ObjLoading::LoadObjDataForZone(&searchPathsForZone, zone); + } + + if (!HandleZone(zone)) + { + return false; + } + + if (m_should_load_obj) + { + ObjLoading::UnloadContainersOfZone(zone); + } + + delete zone; + } + + return true; + } +}; + +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 new file mode 100644 index 00000000..21653e76 --- /dev/null +++ b/src/Unlinker/Unlinker.h @@ -0,0 +1,24 @@ +#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; +}; \ No newline at end of file diff --git a/src/Unlinker/main.cpp b/src/Unlinker/main.cpp index fb1bdd4d..9a326acb 100644 --- a/src/Unlinker/main.cpp +++ b/src/Unlinker/main.cpp @@ -1,163 +1,8 @@ -#include "Utils/Arguments/ArgumentParser.h" -#include "Utils/Arguments/UsageInformation.h" -#include "ZoneLoading.h" -#include "ObjWriting.h" -#include "ContentPrinter.h" -#include "Utils/PathUtils.h" -#include "Utils/FileAPI.h" - -#include -#include "ObjLoading.h" - -const CommandLineOption* optionHelp = CommandLineOption::Builder::Create() - .WithShortName("?") - .WithLongName("help") - .WithDescription("Displays usage information.") - .Build(); - -const CommandLineOption* optionMinimalZoneFile = CommandLineOption::Builder::Create() - .WithShortName("min") - .WithLongName("minimal-zone") - .WithDescription( - "Minimizes the size of the zone file output by only including assets that are not a dependency of another asset.") - .Build(); - -const CommandLineOption* optionList = CommandLineOption::Builder::Create() - .WithShortName("l") - .WithLongName("list") - .WithDescription( - "Lists the contents of a zone instead of writing them to the disk.") - .Build(); - -const CommandLineOption* optionOutputFolder = CommandLineOption::Builder::Create() - .WithShortName("o") - .WithLongName("output-folder") - .WithDescription( - "Specifies the output folder containing the contents of the unlinked zones. Defaults to ./%zoneName%") - .WithParameter("outputFolderPath") - .Build(); - -const CommandLineOption* commandLineOptions[] -{ - optionHelp, - optionMinimalZoneFile, - optionList, - optionOutputFolder -}; - -void PrintUsage() -{ - UsageInformation usage("unlinker.exe"); - - for (auto commandLineOption : commandLineOptions) - { - usage.AddCommandLineOption(commandLineOption); - } - - usage.AddArgument("pathToZone"); - usage.SetVariableArguments(true); - - usage.Print(); -} - -std::string ResolveOutputFolderPath(const std::string& path, Zone* zone) -{ - return std::regex_replace(path, std::regex("%zoneName%"), zone->m_name); -} - -bool HandleZone(Zone* zone, ArgumentParser* argumentParser) -{ - if (argumentParser->IsOptionSpecified(optionList)) - { - const ContentPrinter printer(zone); - printer.PrintContent(); - } - else - { - const bool minimalisticZoneDefinition = argumentParser->IsOptionSpecified(optionMinimalZoneFile); - - std::string outputFolderPath; - - if (argumentParser->IsOptionSpecified(optionOutputFolder)) - { - outputFolderPath = ResolveOutputFolderPath(argumentParser->GetValueForOption(optionOutputFolder), zone); - } - else - { - outputFolderPath = ResolveOutputFolderPath("./%zoneName%", zone); - } - - FileAPI::DirectoryCreate(outputFolderPath); - - const std::string zoneDefinitionFileFolder = utils::Path::Combine(outputFolderPath, "zone_source"); - FileAPI::DirectoryCreate(zoneDefinitionFileFolder); - - FileAPI::File zoneDefinitionFile = FileAPI::Open( - utils::Path::Combine(zoneDefinitionFileFolder, zone->m_name + ".zone"), - FileAPI::Mode::MODE_WRITE); - - if (zoneDefinitionFile.IsOpen()) - { - ObjWriting::WriteZoneDefinition(zone, &zoneDefinitionFile, minimalisticZoneDefinition); - ObjWriting::DumpZone(zone, outputFolderPath); - } - else - { - printf("Failed to open file for zone definition file of zone '%s'.\n", zone->m_name.c_str()); - return false; - } - - zoneDefinitionFile.Close(); - } - - return true; -} +#include "Unlinker.h" int main(const int argc, const char** argv) { - ArgumentParser argumentParser(commandLineOptions, _countof(commandLineOptions)); + Unlinker unlinker; - if (!argumentParser.ParseArguments(argc, argv)) - { - PrintUsage(); - return 1; - } - - if (argumentParser.IsOptionSpecified(optionHelp)) - { - PrintUsage(); - return 0; - } - - const std::vector arguments = argumentParser.GetArguments(); - const size_t argCount = arguments.size(); - if (argCount <= 1) - { - PrintUsage(); - return 1; - } - - for (unsigned argIndex = 1; argIndex < argCount; argIndex++) - { - const std::string& zonePath = arguments[argIndex]; - - Zone* zone = ZoneLoading::LoadZone(zonePath); - if (zone == nullptr) - { - printf("Failed to load zone '%s'.\n", zonePath.c_str()); - return 1; - } - - ObjLoading::LoadReferencedContainersForZone(zone); - ObjLoading::LoadObjDataForZone(zone); - - if(!HandleZone(zone, &argumentParser)) - { - return 1; - } - - delete zone; - } - - return 0; + return unlinker.Start(argc, argv) ? 0 : 1; }