From e6a91c03055bbff0c82ef38d595f35960c355ca4 Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 8 Mar 2021 12:46:27 +0100 Subject: [PATCH] add linker basis --- src/Linker.lua | 8 +- src/Linker/Linker.cpp | 224 ++++++++++++++++++++++ src/Linker/Linker.h | 24 +++ src/Linker/LinkerArgs.cpp | 133 +++++++++++++ src/Linker/LinkerArgs.h | 38 ++++ src/Linker/main.cpp | 10 +- src/ObjCommon/Sound/WavTypes.h | 8 +- src/ObjLoading/ObjContainer/IPak/IPak.cpp | 2 +- src/Unlinker/UnlinkerArgs.cpp | 63 +----- src/Unlinker/UnlinkerArgs.h | 8 - src/Utils/Utils/FileUtils.cpp | 63 ++++++ src/Utils/Utils/FileUtils.h | 26 ++- src/ZoneWriting/ZoneWriting.cpp | 6 + src/ZoneWriting/ZoneWriting.h | 9 + 14 files changed, 538 insertions(+), 84 deletions(-) create mode 100644 src/Linker/Linker.cpp create mode 100644 src/Linker/Linker.h create mode 100644 src/Linker/LinkerArgs.cpp create mode 100644 src/Linker/LinkerArgs.h create mode 100644 src/Utils/Utils/FileUtils.cpp create mode 100644 src/ZoneWriting/ZoneWriting.cpp create mode 100644 src/ZoneWriting/ZoneWriting.h diff --git a/src/Linker.lua b/src/Linker.lua index a858f3fc..d5c932d5 100644 --- a/src/Linker.lua +++ b/src/Linker.lua @@ -38,9 +38,15 @@ function Linker:project() self:include(includes) Utils:include(includes) + ZoneLoading:include(includes) + ObjLoading:include(includes) + ObjWriting:include(includes) ZoneWriting:include(includes) links:linkto(Utils) - --links:linkto(ZoneWriting) + links:linkto(ZoneLoading) + links:linkto(ZoneWriting) + links:linkto(ObjLoading) + links:linkto(ObjWriting) links:linkall() end diff --git a/src/Linker/Linker.cpp b/src/Linker/Linker.cpp new file mode 100644 index 00000000..d34ae922 --- /dev/null +++ b/src/Linker/Linker.cpp @@ -0,0 +1,224 @@ +#include "Linker.h" + +#include +#include +#include +#include + +#include "Utils/ClassUtils.h" +#include "Utils/Arguments/ArgumentParser.h" +#include "ZoneLoading.h" +#include "ObjWriting.h" +#include "ObjLoading.h" +#include "SearchPath/SearchPaths.h" +#include "SearchPath/SearchPathFilesystem.h" +#include "ObjContainer/IWD/IWD.h" +#include "LinkerArgs.h" + +#include "Utils/ObjFileStream.h" + +namespace fs = std::filesystem; + +class Linker::Impl +{ + LinkerArgs m_args; + SearchPaths m_search_paths; + SearchPathFilesystem* m_last_zone_search_path; + std::set m_absolute_search_paths; + + /** + * \brief Loads a search path. + * \param searchPath The search path to load. + */ + void LoadSearchPath(ISearchPath* searchPath) const + { + if (m_args.m_verbose) + { + printf("Loading search path: \"%s\"\n", searchPath->GetPath().c_str()); + } + + ObjLoading::LoadIWDsInSearchPath(searchPath); + } + + /** + * \brief Unloads a search path. + * \param searchPath The search path to unload. + */ + void UnloadSearchPath(ISearchPath* searchPath) const + { + if (m_args.m_verbose) + { + printf("Unloading search path: \"%s\"\n", searchPath->GetPath().c_str()); + } + + ObjLoading::UnloadIWDsInSearchPath(searchPath); + } + + /** + * \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); + } + 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); + } + + for (auto* iwd : IWD::Repository) + { + searchPathsForZone.IncludeSearchPath(iwd); + } + + return searchPathsForZone; + } + + /** + * \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 BuildSearchPaths() + { + for (const auto& path : m_args.m_user_search_paths) + { + auto absolutePath = fs::absolute(path); + + if (!fs::is_directory(absolutePath)) + { + printf("Could not find directory of search path: \"%s\"\n", path.c_str()); + return false; + } + + auto searchPath = std::make_unique(absolutePath.string()); + LoadSearchPath(searchPath.get()); + m_search_paths.CommitSearchPath(std::move(searchPath)); + + m_absolute_search_paths.insert(absolutePath.string()); + } + + 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) + { + printf(" \"%s\"\n", absoluteSearchPath.c_str()); + } + + if (!m_absolute_search_paths.empty()) + { + puts(""); + } + } + + return true; + } + + /** + * \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 BuildZone(const std::string& zoneName) const + { + return true; + } + +public: + Impl() + { + m_last_zone_search_path = nullptr; + } + + /** + * \copydoc Linker::Start + */ + bool Start(const int argc, const char** argv) + { + if (!m_args.ParseArgs(argc, argv)) + return false; + + if (!BuildSearchPaths()) + return false; + + std::vector> zones; + + for (const auto& zonePath : m_args.m_zones_to_load) + { + if (!fs::is_regular_file(zonePath)) + { + printf("Could not find file \"%s\".\n", zonePath.c_str()); + continue; + } + + auto absoluteZoneDirectory = absolute(std::filesystem::path(zonePath).remove_filename()).string(); + + auto searchPathsForZone = GetSearchPathsForZone(absoluteZoneDirectory); + searchPathsForZone.IncludeSearchPath(&m_search_paths); + + auto zone = std::unique_ptr(ZoneLoading::LoadZone(zonePath)); + if (zone == nullptr) + { + printf("Failed to load zone \"%s\".\n", zonePath.c_str()); + return false; + } + + if (m_args.m_verbose) + { + printf("Loaded zone \"%s\"\n", zone->m_name.c_str()); + } + + ObjLoading::LoadReferencedContainersForZone(&searchPathsForZone, zone.get()); + ObjLoading::LoadObjDataForZone(&searchPathsForZone, zone.get()); + } + + auto result = true; + for(const auto& zone : m_args.m_zones_to_build) + { + if (!BuildZone(zone)) + { + result = false; + break; + } + } + + for(const auto& zone : zones) + { + ObjLoading::UnloadContainersOfZone(zone.get()); + } + zones.clear(); + + return result; + } +}; + +Linker::Linker() +{ + m_impl = new Impl(); +} + +Linker::~Linker() +{ + delete m_impl; + m_impl = nullptr; +} + +bool Linker::Start(const int argc, const char** argv) const +{ + return m_impl->Start(argc, argv); +} diff --git a/src/Linker/Linker.h b/src/Linker/Linker.h new file mode 100644 index 00000000..3e452c90 --- /dev/null +++ b/src/Linker/Linker.h @@ -0,0 +1,24 @@ +#pragma once + +class Linker +{ + class Impl; + Impl* m_impl; + +public: + Linker(); + ~Linker(); + + Linker(const Linker& other) = delete; + Linker(Linker&& other) noexcept = delete; + Linker& operator=(const Linker& other) = delete; + Linker& operator=(Linker&& other) noexcept = delete; + + /** + * \brief Starts the Linker 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/Linker/LinkerArgs.cpp b/src/Linker/LinkerArgs.cpp new file mode 100644 index 00000000..3f111366 --- /dev/null +++ b/src/Linker/LinkerArgs.cpp @@ -0,0 +1,133 @@ +#include "LinkerArgs.h" + +#include +#include + +#include "Utils/Arguments/UsageInformation.h" +#include "ObjLoading.h" +#include "ObjWriting.h" +#include "Utils/FileUtils.h" + +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_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_LOAD = + CommandLineOption::Builder::Create() + .WithShortName("l") + .WithLongName("load") + .WithDescription("Loads an existing zone to be able to use its assets when building.") + .WithParameter("zonePath") + .Reusable() + .Build(); + +const CommandLineOption* const COMMAND_LINE_OPTIONS[] +{ + OPTION_HELP, + OPTION_VERBOSE, + OPTION_OUTPUT_FOLDER, + OPTION_SEARCH_PATH, + OPTION_LOAD +}; + +LinkerArgs::LinkerArgs() + : m_argument_parser(COMMAND_LINE_OPTIONS, std::extent::value), + m_output_folder("zone_out/%zoneName%"), + m_verbose(false) +{ +} + +void LinkerArgs::PrintUsage() +{ + UsageInformation usage("Linker.exe"); + + for (const auto* commandLineOption : COMMAND_LINE_OPTIONS) + { + usage.AddCommandLineOption(commandLineOption); + } + + usage.AddArgument("zoneName"); + usage.SetVariableArguments(true); + + usage.Print(); +} + +void LinkerArgs::SetVerbose(const bool isVerbose) +{ + m_verbose = isVerbose; + ObjLoading::Configuration.Verbose = isVerbose; + ObjWriting::Configuration.Verbose = isVerbose; +} + +bool LinkerArgs::ParseArgs(const int argc, const char** argv) +{ + if (!m_argument_parser.ParseArguments(argc - 1, &argv[1])) + { + PrintUsage(); + return false; + } + + // Check if the user requested help + if (m_argument_parser.IsOptionSpecified(OPTION_HELP)) + { + PrintUsage(); + return false; + } + + m_zones_to_build = m_argument_parser.GetArguments(); + if (m_zones_to_build.empty()) + { + // No zones to build specified... + PrintUsage(); + return false; + } + + // -v; --verbose + SetVerbose(m_argument_parser.IsOptionSpecified(OPTION_VERBOSE)); + + // -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 (!FileUtils::ParsePathsString(m_argument_parser.GetValueForOption(OPTION_SEARCH_PATH), m_user_search_paths)) + return false; + } + + if(m_argument_parser.IsOptionSpecified(OPTION_LOAD)) + m_zones_to_load = m_argument_parser.GetParametersForOption(OPTION_LOAD); + + return true; +} + +std::string LinkerArgs::GetOutputFolderPathForZone(const std::string& zoneName) const +{ + return std::regex_replace(m_output_folder, std::regex("%zoneName%"), zoneName); +} diff --git a/src/Linker/LinkerArgs.h b/src/Linker/LinkerArgs.h new file mode 100644 index 00000000..80df390f --- /dev/null +++ b/src/Linker/LinkerArgs.h @@ -0,0 +1,38 @@ +#pragma once +#include +#include + +#include "Utils/ClassUtils.h" +#include "Utils/Arguments/ArgumentParser.h" +#include "Zone/Zone.h" + +class LinkerArgs +{ + ArgumentParser m_argument_parser; + + /** + * \brief Prints a command line usage help text for the Linker tool to stdout. + */ + static void PrintUsage(); + + void SetVerbose(bool isVerbose); + +public: + std::vector m_zones_to_load; + std::vector m_zones_to_build; + std::set m_user_search_paths; + + std::string m_output_folder; + + bool m_verbose; + + LinkerArgs(); + 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 zoneName The name of the zone to resolve the path input for. + * \return An output path for the zone based on the user input. + */ + _NODISCARD std::string GetOutputFolderPathForZone(const std::string& zoneName) const; +}; diff --git a/src/Linker/main.cpp b/src/Linker/main.cpp index 3e90c6cf..6e78bec1 100644 --- a/src/Linker/main.cpp +++ b/src/Linker/main.cpp @@ -1,4 +1,8 @@ -int main(int argc, const char** argv) +#include "Linker.h" + +int main(const int argc, const char** argv) { - return 0; -} \ No newline at end of file + Linker linker; + + return linker.Start(argc, argv) ? 0 : 1; +} diff --git a/src/ObjCommon/Sound/WavTypes.h b/src/ObjCommon/Sound/WavTypes.h index ebcd93e6..e5cd145d 100644 --- a/src/ObjCommon/Sound/WavTypes.h +++ b/src/ObjCommon/Sound/WavTypes.h @@ -3,10 +3,10 @@ #include "Utils/FileUtils.h" -constexpr uint32_t WAV_WAVE_ID = MakeMagic32('W', 'A', 'V', 'E'); -constexpr uint32_t WAV_CHUNK_ID_RIFF = MakeMagic32('R', 'I', 'F', 'F'); -constexpr uint32_t WAV_CHUNK_ID_FMT = MakeMagic32('f', 'm', 't', ' '); -constexpr uint32_t WAV_CHUNK_ID_DATA = MakeMagic32('d', 'a', 't', 'a'); +constexpr uint32_t WAV_WAVE_ID = FileUtils::MakeMagic32('W', 'A', 'V', 'E'); +constexpr uint32_t WAV_CHUNK_ID_RIFF = FileUtils::MakeMagic32('R', 'I', 'F', 'F'); +constexpr uint32_t WAV_CHUNK_ID_FMT = FileUtils::MakeMagic32('f', 'm', 't', ' '); +constexpr uint32_t WAV_CHUNK_ID_DATA = FileUtils::MakeMagic32('d', 'a', 't', 'a'); struct WavChunkHeader { diff --git a/src/ObjLoading/ObjContainer/IPak/IPak.cpp b/src/ObjLoading/ObjContainer/IPak/IPak.cpp index e9923c6a..b02f1c4e 100644 --- a/src/ObjLoading/ObjContainer/IPak/IPak.cpp +++ b/src/ObjLoading/ObjContainer/IPak/IPak.cpp @@ -18,7 +18,7 @@ ObjContainerRepository IPak::Repository; class IPak::Impl : public ObjContainerReferenceable { - static const uint32_t MAGIC = MakeMagic32('K', 'A', 'P', 'I'); + static const uint32_t MAGIC = FileUtils::MakeMagic32('K', 'A', 'P', 'I'); static const uint32_t VERSION = 0x50000; std::string m_path; diff --git a/src/Unlinker/UnlinkerArgs.cpp b/src/Unlinker/UnlinkerArgs.cpp index 717ddff8..61682225 100644 --- a/src/Unlinker/UnlinkerArgs.cpp +++ b/src/Unlinker/UnlinkerArgs.cpp @@ -6,6 +6,7 @@ #include "Utils/Arguments/UsageInformation.h" #include "ObjLoading.h" #include "ObjWriting.h" +#include "Utils/FileUtils.h" const CommandLineOption* const OPTION_HELP = CommandLineOption::Builder::Create() @@ -84,66 +85,6 @@ UnlinkerArgs::UnlinkerArgs() 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"); @@ -231,7 +172,7 @@ bool UnlinkerArgs::ParseArgs(const int argc, const char** argv) // --search-path if (m_argument_parser.IsOptionSpecified(OPTION_SEARCH_PATH)) { - if (!ParsePathsString(m_argument_parser.GetValueForOption(OPTION_SEARCH_PATH), m_user_search_paths)) + if (!FileUtils::ParsePathsString(m_argument_parser.GetValueForOption(OPTION_SEARCH_PATH), m_user_search_paths)) { return false; } diff --git a/src/Unlinker/UnlinkerArgs.h b/src/Unlinker/UnlinkerArgs.h index b1e75f77..5a454932 100644 --- a/src/Unlinker/UnlinkerArgs.h +++ b/src/Unlinker/UnlinkerArgs.h @@ -14,14 +14,6 @@ class UnlinkerArgs */ 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(); diff --git a/src/Utils/Utils/FileUtils.cpp b/src/Utils/Utils/FileUtils.cpp new file mode 100644 index 00000000..24b8a612 --- /dev/null +++ b/src/Utils/Utils/FileUtils.cpp @@ -0,0 +1,63 @@ +#include "FileUtils.h" + +#include + +bool FileUtils::ParsePathsString(const std::string& pathsString, std::set& output) +{ + std::ostringstream currentPath; + auto pathStart = true; + auto 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) + { + auto 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; +} diff --git a/src/Utils/Utils/FileUtils.h b/src/Utils/Utils/FileUtils.h index 1ce5f123..e96178b6 100644 --- a/src/Utils/Utils/FileUtils.h +++ b/src/Utils/Utils/FileUtils.h @@ -1,10 +1,24 @@ #pragma once #include +#include +#include -constexpr uint32_t MakeMagic32(const char ch0, const char ch1, const char ch2, const char ch3) +class FileUtils { - return static_cast(ch0) - | static_cast(ch1) << 8 - | static_cast(ch2) << 16 - | static_cast(ch3) << 24; -} +public: + static constexpr uint32_t MakeMagic32(const char ch0, const char ch1, const char ch2, const char ch3) + { + return static_cast(ch0) + | static_cast(ch1) << 8 + | static_cast(ch2) << 16 + | static_cast(ch3) << 24; + } + + /** + * \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); +}; diff --git a/src/ZoneWriting/ZoneWriting.cpp b/src/ZoneWriting/ZoneWriting.cpp new file mode 100644 index 00000000..a19d79d0 --- /dev/null +++ b/src/ZoneWriting/ZoneWriting.cpp @@ -0,0 +1,6 @@ +#include "ZoneWriting.h" + +bool ZoneWriting::WriteZone(Zone* zone) +{ + return true; +} diff --git a/src/ZoneWriting/ZoneWriting.h b/src/ZoneWriting/ZoneWriting.h new file mode 100644 index 00000000..680d82dc --- /dev/null +++ b/src/ZoneWriting/ZoneWriting.h @@ -0,0 +1,9 @@ +#pragma once +#include +#include "Zone/Zone.h" + +class ZoneWriting +{ +public: + static bool WriteZone(Zone* zone); +}; \ No newline at end of file