From 23f77bb3352b7ef10bf1075491794a1d378c2d15 Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 14 Feb 2020 23:40:47 +0100 Subject: [PATCH] Unlinker: Make parsing specified command line arguments its own class --- src/ObjLoading/Game/T6/ObjLoaderT6.cpp | 8 +- src/ObjLoading/Game/T6/ObjLoaderT6.h | 8 +- src/ObjLoading/IObjLoader.h | 8 +- src/ObjLoading/ObjLoading.cpp | 8 +- src/ObjWriting/Dumping/IZoneDumper.h | 6 +- src/ObjWriting/Game/T6/ZoneDumperT6.cpp | 6 +- src/ObjWriting/Game/T6/ZoneDumperT6.h | 6 +- src/ObjWriting/ObjWriting.cpp | 8 +- src/ObjWriting/ObjWriting.h | 17 +- src/Unlinker/Unlinker.cpp | 262 +++---------------- src/Unlinker/UnlinkerArgs.cpp | 242 +++++++++++++++++ src/Unlinker/UnlinkerArgs.h | 52 ++++ src/Utils/Utils/Arguments/ArgumentParser.cpp | 2 +- src/Utils/Utils/Arguments/ArgumentParser.h | 2 +- 14 files changed, 373 insertions(+), 262 deletions(-) create mode 100644 src/Unlinker/UnlinkerArgs.cpp create mode 100644 src/Unlinker/UnlinkerArgs.h diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index 4699ac13..3d63ec77 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -26,7 +26,7 @@ int ObjLoaderT6::Com_HashKey(const char* str, const int maxLen) return hash ^ ((hash ^ (hash >> 10)) >> 10); } -bool ObjLoaderT6::SupportsZone(Zone* zone) +bool ObjLoaderT6::SupportsZone(Zone* zone) const { return zone->m_game == &g_GameT6; } @@ -119,7 +119,7 @@ void ObjLoaderT6::LoadCommonIPaks(ISearchPath* searchPath, Zone* zone) } } -void ObjLoaderT6::LoadReferencedContainersForZone(ISearchPath* searchPath, Zone* zone) +void ObjLoaderT6::LoadReferencedContainersForZone(ISearchPath* searchPath, Zone* zone) const { auto* assetPoolT6 = dynamic_cast(zone->GetPools()); const int zoneNameHash = Com_HashKey(zone->m_name.c_str(), 64); @@ -144,7 +144,7 @@ void ObjLoaderT6::LoadReferencedContainersForZone(ISearchPath* searchPath, Zone* } } -void ObjLoaderT6::UnloadContainersOfZone(Zone* zone) +void ObjLoaderT6::UnloadContainersOfZone(Zone* zone) const { IPak::Repository.RemoveContainerReferences(zone); } @@ -242,7 +242,7 @@ void ObjLoaderT6::LoadImageData(ISearchPath* searchPath, Zone* zone) } } -void ObjLoaderT6::LoadObjDataForZone(ISearchPath* searchPath, Zone* zone) +void ObjLoaderT6::LoadObjDataForZone(ISearchPath* searchPath, Zone* zone) const { LoadImageData(searchPath, zone); } diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.h b/src/ObjLoading/Game/T6/ObjLoaderT6.h index 3621d3d7..c8e7989b 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.h +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.h @@ -21,10 +21,10 @@ class ObjLoaderT6 final : public IObjLoader static void LoadCommonIPaks(ISearchPath* searchPath, Zone* zone); public: - bool SupportsZone(Zone* zone) override; + bool SupportsZone(Zone* zone) const override; - void LoadReferencedContainersForZone(ISearchPath* searchPath, Zone* zone) override; - void UnloadContainersOfZone(Zone* zone) override; + void LoadReferencedContainersForZone(ISearchPath* searchPath, Zone* zone) const override; + void UnloadContainersOfZone(Zone* zone) const override; - void LoadObjDataForZone(ISearchPath* searchPath, Zone* zone) override; + void LoadObjDataForZone(ISearchPath* searchPath, Zone* zone) const override; }; diff --git a/src/ObjLoading/IObjLoader.h b/src/ObjLoading/IObjLoader.h index efb274f0..f55b7969 100644 --- a/src/ObjLoading/IObjLoader.h +++ b/src/ObjLoading/IObjLoader.h @@ -13,25 +13,25 @@ public: * \param zone The zone to check. * \return \c true if the specified zone is supported. */ - virtual bool SupportsZone(Zone* zone) = 0; + virtual bool SupportsZone(Zone* zone) const = 0; /** * \brief Loads all containers that are referenced by a specified zone. * \param searchPath The search path object to use to find the referenced containers. * \param zone The zone to check for referenced containers. */ - virtual void LoadReferencedContainersForZone(ISearchPath* searchPath, Zone* zone) = 0; + virtual void LoadReferencedContainersForZone(ISearchPath* searchPath, Zone* zone) const = 0; /** * \brief Unloads all containers of a specified zone. If a container is also loaded by another zone it will only be unloaded when all referencing zones are unloaded. * \param zone The zone to unload all containers for. */ - virtual void UnloadContainersOfZone(Zone* zone) = 0; + virtual void UnloadContainersOfZone(Zone* zone) const = 0; /** * \brief Loads the obj data for all assets of a specified zone. * \param searchPath The search path object to use to find obj files. * \param zone The zone of the assets to load the obj data for. */ - virtual void LoadObjDataForZone(ISearchPath* searchPath, Zone* zone) = 0; + virtual void LoadObjDataForZone(ISearchPath* searchPath, Zone* zone) const = 0; }; \ No newline at end of file diff --git a/src/ObjLoading/ObjLoading.cpp b/src/ObjLoading/ObjLoading.cpp index 41284ea4..92627f36 100644 --- a/src/ObjLoading/ObjLoading.cpp +++ b/src/ObjLoading/ObjLoading.cpp @@ -6,14 +6,14 @@ ObjLoading::Configuration_t ObjLoading::Configuration; -IObjLoader* objLoaders[] +const IObjLoader* const OBJ_LOADERS[] { new ObjLoaderT6() }; void ObjLoading::LoadReferencedContainersForZone(ISearchPath* searchPath, Zone* zone) { - for (auto* loader : objLoaders) + for (auto* loader : OBJ_LOADERS) { if (loader->SupportsZone(zone)) { @@ -25,7 +25,7 @@ void ObjLoading::LoadReferencedContainersForZone(ISearchPath* searchPath, Zone* void ObjLoading::LoadObjDataForZone(ISearchPath* searchPath, Zone* zone) { - for (auto* loader : objLoaders) + for (auto* loader : OBJ_LOADERS) { if (loader->SupportsZone(zone)) { @@ -37,7 +37,7 @@ void ObjLoading::LoadObjDataForZone(ISearchPath* searchPath, Zone* zone) void ObjLoading::UnloadContainersOfZone(Zone* zone) { - for (auto* loader : objLoaders) + for (auto* loader : OBJ_LOADERS) { if (loader->SupportsZone(zone)) { diff --git a/src/ObjWriting/Dumping/IZoneDumper.h b/src/ObjWriting/Dumping/IZoneDumper.h index ceb1d48e..683004dc 100644 --- a/src/ObjWriting/Dumping/IZoneDumper.h +++ b/src/ObjWriting/Dumping/IZoneDumper.h @@ -8,7 +8,7 @@ class IZoneDumper public: virtual ~IZoneDumper() = default; - virtual bool CanHandleZone(Zone* zone) = 0; - virtual bool DumpZone(Zone* zone, const std::string& basePath) = 0; - virtual bool WriteZoneDefinition(Zone* zone, FileAPI::File* file, bool minimalistic) = 0; + virtual bool CanHandleZone(Zone* zone) const = 0; + virtual bool DumpZone(Zone* zone, const std::string& basePath) const = 0; + virtual bool WriteZoneDefinition(Zone* zone, FileAPI::File* file) const = 0; }; \ No newline at end of file diff --git a/src/ObjWriting/Game/T6/ZoneDumperT6.cpp b/src/ObjWriting/Game/T6/ZoneDumperT6.cpp index 164c52f5..6d3db21d 100644 --- a/src/ObjWriting/Game/T6/ZoneDumperT6.cpp +++ b/src/ObjWriting/Game/T6/ZoneDumperT6.cpp @@ -10,12 +10,12 @@ #include "AssetDumpers/AssetDumperLocalizeEntry.h" #include "AssetDumpers/AssetDumperGfxImage.h" -bool ZoneDumperT6::CanHandleZone(Zone* zone) +bool ZoneDumperT6::CanHandleZone(Zone* zone) const { return zone->m_game == &g_GameT6; } -bool ZoneDumperT6::DumpZone(Zone* zone, const std::string& basePath) +bool ZoneDumperT6::DumpZone(Zone* zone, const std::string& basePath) const { #define DUMP_ASSET_POOL(dumperType, poolName) \ if(assetPools->poolName) \ @@ -80,7 +80,7 @@ bool ZoneDumperT6::DumpZone(Zone* zone, const std::string& basePath) #undef DUMP_ASSET_POOL } -bool ZoneDumperT6::WriteZoneDefinition(Zone* zone, FileAPI::File* file, bool minimalistic) +bool ZoneDumperT6::WriteZoneDefinition(Zone* zone, FileAPI::File* file) const { return true; } \ No newline at end of file diff --git a/src/ObjWriting/Game/T6/ZoneDumperT6.h b/src/ObjWriting/Game/T6/ZoneDumperT6.h index 6ef5da6e..84d3d399 100644 --- a/src/ObjWriting/Game/T6/ZoneDumperT6.h +++ b/src/ObjWriting/Game/T6/ZoneDumperT6.h @@ -4,7 +4,7 @@ class ZoneDumperT6 final : public IZoneDumper { public: - bool CanHandleZone(Zone* zone) override; - bool DumpZone(Zone* zone, const std::string& basePath) override; - bool WriteZoneDefinition(Zone* zone, FileAPI::File* file, bool minimalistic) override; + bool CanHandleZone(Zone* zone) const override; + bool DumpZone(Zone* zone, const std::string& basePath) const override; + bool WriteZoneDefinition(Zone* zone, FileAPI::File* file) const override; }; diff --git a/src/ObjWriting/ObjWriting.cpp b/src/ObjWriting/ObjWriting.cpp index 780db7b2..7a918765 100644 --- a/src/ObjWriting/ObjWriting.cpp +++ b/src/ObjWriting/ObjWriting.cpp @@ -2,14 +2,16 @@ #include "Dumping/IZoneDumper.h" #include "Game/T6/ZoneDumperT6.h" -IZoneDumper* zoneDumper[] +ObjWriting::Configuration_t ObjWriting::Configuration; + +const IZoneDumper* const ZONE_DUMPER[] { new ZoneDumperT6() }; bool ObjWriting::DumpZone(Zone* zone, const std::string& basePath) { - for (auto dumper : zoneDumper) + for (auto dumper : ZONE_DUMPER) { if (dumper->CanHandleZone(zone)) { @@ -26,7 +28,7 @@ bool ObjWriting::DumpZone(Zone* zone, const std::string& basePath) return false; } -bool ObjWriting::WriteZoneDefinition(Zone* zone, FileAPI::File* file, bool minimalistic) +bool ObjWriting::WriteZoneDefinition(Zone* zone, FileAPI::File* file) { return file->Printf("// %s", "Insert zone definition here") > 0; } \ No newline at end of file diff --git a/src/ObjWriting/ObjWriting.h b/src/ObjWriting/ObjWriting.h index 3f1386ed..ebd57f87 100644 --- a/src/ObjWriting/ObjWriting.h +++ b/src/ObjWriting/ObjWriting.h @@ -7,6 +7,21 @@ class ObjWriting { public: + static class Configuration_t + { + public: + enum class ImageOutputFormat_e + { + DDS, + IWI + }; + + bool Verbose = false; + ImageOutputFormat_e ImageOutputFormat = ImageOutputFormat_e::DDS; + bool MinimalZoneFileOutput = false; + + } Configuration; + static bool DumpZone(Zone* zone, const std::string& basePath); - static bool WriteZoneDefinition(Zone* zone, FileAPI::File* file, bool minimalistic); + static bool WriteZoneDefinition(Zone* zone, FileAPI::File* file); }; diff --git a/src/Unlinker/Unlinker.cpp b/src/Unlinker/Unlinker.cpp index c2bc9776..5d754124 100644 --- a/src/Unlinker/Unlinker.cpp +++ b/src/Unlinker/Unlinker.cpp @@ -1,7 +1,6 @@ #include "Unlinker.h" #include "Utils/Arguments/ArgumentParser.h" -#include "Utils/Arguments/UsageInformation.h" #include "ZoneLoading.h" #include "ObjWriting.h" #include "ContentPrinter.h" @@ -15,67 +14,19 @@ #include #include #include "ObjContainer/IWD/IWD.h" - -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 -}; +#include "UnlinkerArgs.h" class Unlinker::Impl { + UnlinkerArgs m_args; 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; + bool ShouldLoadObj() const + { + return m_args.m_task != UnlinkerArgs::ProcessingTask::LIST; + } /** * \brief Loads a search path. @@ -83,9 +34,9 @@ class Unlinker::Impl */ void LoadSearchPath(ISearchPath* searchPath) const { - if (m_should_load_obj) + if (ShouldLoadObj()) { - if (m_verbose) + if (m_args.m_verbose) { printf("Loading search path: \"%s\"\n", searchPath->GetPath().c_str()); } @@ -100,9 +51,9 @@ class Unlinker::Impl */ void UnloadSearchPath(ISearchPath* searchPath) const { - if (m_should_load_obj) + if (ShouldLoadObj()) { - if (m_verbose) + if (m_args.m_verbose) { printf("Unloading search path: \"%s\"\n", searchPath->GetPath().c_str()); } @@ -141,105 +92,30 @@ class Unlinker::Impl 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)) + for (const auto& path : m_args.m_user_search_paths) { - std::set pathList; - if (!ParsePathsString(m_argument_parser.GetValueForOption(optionSearchPath), pathList)) + std::string absolutePath = std::filesystem::absolute(path).string(); + + if (!FileAPI::DirectoryExists(absolutePath)) { + printf("Could not find directory of search path: \"%s\"\n", path.c_str()); return false; } - for (const auto& path : pathList) - { - std::string absolutePath = std::filesystem::absolute(path).string(); + auto* searchPath = new SearchPathFilesystem(absolutePath); + LoadSearchPath(searchPath); + m_search_paths.CommitSearchPath(searchPath); - if (!FileAPI::DirectoryExists(absolutePath)) - { - printf("Could not find directory of search path: \"%s\"\n", path.c_str()); - return false; - } - - auto* searchPath = new SearchPathFilesystem(absolutePath); - LoadSearchPath(searchPath); - m_search_paths.CommitSearchPath(searchPath); - - m_absolute_search_paths.insert(absolutePath); - } + m_absolute_search_paths.insert(absolutePath); } - if (m_verbose) + if (m_args.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) @@ -256,62 +132,21 @@ class Unlinker::Impl 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) + bool HandleZone(Zone* zone) const { - if (m_argument_parser.IsOptionSpecified(optionList)) + if (m_args.m_task == UnlinkerArgs::ProcessingTask::LIST) { const ContentPrinter printer(zone); printer.PrintContent(); } - else + else if (m_args.m_task == UnlinkerArgs::ProcessingTask::DUMP) { - 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); - } - + const std::string outputFolderPath = m_args.GetOutputFolderPathForZone(zone); FileAPI::DirectoryCreate(outputFolderPath); const std::string zoneDefinitionFileFolder = utils::Path::Combine(outputFolderPath, "zone_source"); @@ -323,7 +158,7 @@ class Unlinker::Impl if (zoneDefinitionFile.IsOpen()) { - ObjWriting::WriteZoneDefinition(zone, &zoneDefinitionFile, minimalisticZoneDefinition); + ObjWriting::WriteZoneDefinition(zone, &zoneDefinitionFile); ObjWriting::DumpZone(zone, outputFolderPath); } else @@ -338,19 +173,10 @@ class Unlinker::Impl return true; } - void SetVerbose(const bool verbose) - { - m_verbose = verbose; - ObjLoading::Configuration.Verbose = verbose; - } - public: Impl() - : m_argument_parser(commandLineOptions, _countof(commandLineOptions)) { m_last_zone_search_path = nullptr; - m_verbose = false; - m_should_load_obj = false; } /** @@ -358,41 +184,15 @@ public: */ bool Start(const int argc, const char** argv) { - if (!m_argument_parser.ParseArguments(argc, argv)) - { - PrintUsage(); + if (!m_args.ParseArgs(argc, argv)) return false; - } - - SetVerbose(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++) + for (const auto& zonePath : m_args.m_zones_to_load) { - const std::string& zonePath = arguments[argIndex]; - - if(!FileAPI::FileExists(zonePath)) + if (!FileAPI::FileExists(zonePath)) { printf("Could not find file \"%s\".\n", zonePath.c_str()); continue; @@ -410,12 +210,12 @@ public: return false; } - if (m_verbose) + if (m_args.m_verbose) { printf("Loaded zone \"%s\"\n", zone->m_name.c_str()); } - if (m_should_load_obj) + if (ShouldLoadObj()) { ObjLoading::LoadReferencedContainersForZone(&searchPathsForZone, zone); ObjLoading::LoadObjDataForZone(&searchPathsForZone, zone); @@ -426,7 +226,7 @@ public: return false; } - if (m_should_load_obj) + if (ShouldLoadObj()) { ObjLoading::UnloadContainersOfZone(zone); } diff --git a/src/Unlinker/UnlinkerArgs.cpp b/src/Unlinker/UnlinkerArgs.cpp new file mode 100644 index 00000000..17126603 --- /dev/null +++ b/src/Unlinker/UnlinkerArgs.cpp @@ -0,0 +1,242 @@ +#include "UnlinkerArgs.h" +#include "Utils/Arguments/UsageInformation.h" +#include "ObjLoading.h" +#include "ObjWriting.h" +#include + +const CommandLineOption* const OPTION_HELP = CommandLineOption::Builder::Create() + .WithShortName("?") + .WithLongName("help") + .WithDescription("Displays usage information.") + .Build(); + +const CommandLineOption* const OPTION_VERBOSE = CommandLineOption::Builder::Create() +.WithShortName("v") +.WithLongName("verbose") +.WithDescription("Outputs a lot more and more detailed messages.") +.Build(); + +const CommandLineOption* const OPTION_MINIMAL_ZONE_FILE = 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* const OPTION_LIST = CommandLineOption::Builder::Create() +.WithShortName("l") +.WithLongName("list") +.WithDescription( + "Lists the contents of a zone instead of writing them to the disk.") + .Build(); + +const CommandLineOption* const OPTION_OUTPUT_FOLDER = 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* const OPTION_SEARCH_PATH = 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* const OPTION_IMAGE_FORMAT = CommandLineOption::Builder::Create() +.WithLongName("image-format") +.WithDescription( + "Specifies the format of dumped image files. Valid values are: DDS, IWI") + .WithParameter("imageFormatValue") + .Build(); + +const CommandLineOption* const COMMAND_LINE_OPTIONS[] +{ + OPTION_HELP, + OPTION_VERBOSE, + OPTION_MINIMAL_ZONE_FILE, + OPTION_LIST, + OPTION_OUTPUT_FOLDER, + OPTION_SEARCH_PATH, + OPTION_IMAGE_FORMAT +}; + +UnlinkerArgs::UnlinkerArgs() + : m_argument_parser(COMMAND_LINE_OPTIONS, _countof(COMMAND_LINE_OPTIONS)) +{ + m_task = ProcessingTask::DUMP; + m_output_folder = "./%zoneName%"; + + m_verbose = false; +} + +bool UnlinkerArgs::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; +} + +void UnlinkerArgs::PrintUsage() +{ + UsageInformation usage("unlinker.exe"); + + for (auto commandLineOption : COMMAND_LINE_OPTIONS) + { + usage.AddCommandLineOption(commandLineOption); + } + + usage.AddArgument("pathToZone"); + usage.SetVariableArguments(true); + + usage.Print(); +} + +void UnlinkerArgs::SetVerbose(const bool isVerbose) +{ + m_verbose = isVerbose; + ObjLoading::Configuration.Verbose = isVerbose; + ObjWriting::Configuration.Verbose = isVerbose; +} + +bool UnlinkerArgs::SetImageDumpingMode() +{ + std::string specifiedValue = m_argument_parser.GetValueForOption(OPTION_IMAGE_FORMAT); + for (auto& c : specifiedValue) + c = tolower(c); + + if(specifiedValue == "dds") + { + ObjWriting::Configuration.ImageOutputFormat = ObjWriting::Configuration_t::ImageOutputFormat_e::DDS; + return true; + } + + if(specifiedValue == "iwi") + { + ObjWriting::Configuration.ImageOutputFormat = ObjWriting::Configuration_t::ImageOutputFormat_e::IWI; + return true; + } + + const std::string originalValue = m_argument_parser.GetValueForOption(OPTION_IMAGE_FORMAT); + printf("Illegal value: \"%s\" is not a valid image output format. Use -? to see usage information.\n", originalValue.c_str()); + return false; +} + +bool UnlinkerArgs::ParseArgs(const int argc, const char** argv) +{ + if (!m_argument_parser.ParseArguments(argc, argv)) + { + PrintUsage(); + return false; + } + + // Check if the user requested help + if (m_argument_parser.IsOptionSpecified(OPTION_HELP)) + { + PrintUsage(); + return false; + } + + 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; + } + + // -v; --verbose + SetVerbose(m_argument_parser.IsOptionSpecified(OPTION_VERBOSE)); + + // -min; --minimal-zone + ObjWriting::Configuration.MinimalZoneFileOutput = m_argument_parser.IsOptionSpecified(OPTION_MINIMAL_ZONE_FILE); + + // -l; --list + if (m_argument_parser.IsOptionSpecified(OPTION_LIST)) + m_task = ProcessingTask::LIST; + + // -o; --output-folder + if (m_argument_parser.IsOptionSpecified(OPTION_OUTPUT_FOLDER)) + m_output_folder = m_argument_parser.GetValueForOption(OPTION_OUTPUT_FOLDER); + + // --search-path + if (m_argument_parser.IsOptionSpecified(OPTION_SEARCH_PATH)) + { + if (!ParsePathsString(m_argument_parser.GetValueForOption(OPTION_SEARCH_PATH), m_user_search_paths)) + { + return false; + } + } + + // --image-format + if(m_argument_parser.IsOptionSpecified(OPTION_IMAGE_FORMAT)) + { + if (!SetImageDumpingMode()) + { + return false; + } + } + + return true; +} + +std::string UnlinkerArgs::GetOutputFolderPathForZone(Zone* zone) const +{ + return std::regex_replace(m_output_folder, std::regex("%zoneName%"), zone->m_name); +} diff --git a/src/Unlinker/UnlinkerArgs.h b/src/Unlinker/UnlinkerArgs.h new file mode 100644 index 00000000..fc4e91e4 --- /dev/null +++ b/src/Unlinker/UnlinkerArgs.h @@ -0,0 +1,52 @@ +#pragma once +#include "Utils/Arguments/ArgumentParser.h" +#include "Zone/Zone.h" + +#include +#include + +class UnlinkerArgs +{ + ArgumentParser m_argument_parser; + + /** + * \brief Prints a command line usage help text for the Unlinker tool to stdout. + */ + static void PrintUsage(); + + /** + * \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); + + void SetVerbose(bool isVerbose); + bool SetImageDumpingMode(); + +public: + enum class ProcessingTask + { + DUMP, + LIST + }; + + std::vector m_zones_to_load; + std::set m_user_search_paths; + + ProcessingTask m_task; + std::string m_output_folder; + + bool m_verbose; + + UnlinkerArgs(); + bool ParseArgs(int argc, const char** argv); + + /** + * \brief Converts the output path specified by command line arguments to a path applies for the specified zone. + * \param zone The zone to resolve the path input for. + * \return An output path for the zone based on the user input. + */ + std::string GetOutputFolderPathForZone(Zone* zone) const; +}; diff --git a/src/Utils/Utils/Arguments/ArgumentParser.cpp b/src/Utils/Utils/Arguments/ArgumentParser.cpp index 9ec8511a..fe3fabd1 100644 --- a/src/Utils/Utils/Arguments/ArgumentParser.cpp +++ b/src/Utils/Utils/Arguments/ArgumentParser.cpp @@ -4,7 +4,7 @@ const std::string PREFIX_LONG = "--"; const std::string PREFIX_SHORT = "-"; -ArgumentParser::ArgumentParser(const CommandLineOption** options, const size_t optionCount) +ArgumentParser::ArgumentParser(const CommandLineOption* const* options, const size_t optionCount) { for(unsigned optionIndex = 0; optionIndex < optionCount; optionIndex++) { diff --git a/src/Utils/Utils/Arguments/ArgumentParser.h b/src/Utils/Utils/Arguments/ArgumentParser.h index bc9ebe97..7e23d65d 100644 --- a/src/Utils/Utils/Arguments/ArgumentParser.h +++ b/src/Utils/Utils/Arguments/ArgumentParser.h @@ -11,7 +11,7 @@ class ArgumentParser std::vector m_matched_arguments; public: - ArgumentParser(const CommandLineOption** options, size_t optionCount); + ArgumentParser(const CommandLineOption* const* options, size_t optionCount); bool ParseArguments(std::vector& args); bool ParseArguments(int argc, const char** argv);