add linker basis

This commit is contained in:
Jan 2021-03-08 12:46:27 +01:00
parent 39a1485be6
commit e6a91c0305
14 changed files with 538 additions and 84 deletions

View File

@ -38,9 +38,15 @@ function Linker:project()
self:include(includes) self:include(includes)
Utils:include(includes) Utils:include(includes)
ZoneLoading:include(includes)
ObjLoading:include(includes)
ObjWriting:include(includes)
ZoneWriting:include(includes) ZoneWriting:include(includes)
links:linkto(Utils) links:linkto(Utils)
--links:linkto(ZoneWriting) links:linkto(ZoneLoading)
links:linkto(ZoneWriting)
links:linkto(ObjLoading)
links:linkto(ObjWriting)
links:linkall() links:linkall()
end end

224
src/Linker/Linker.cpp Normal file
View File

@ -0,0 +1,224 @@
#include "Linker.h"
#include <set>
#include <regex>
#include <filesystem>
#include <fstream>
#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<std::string> 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<SearchPathFilesystem>(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<std::unique_ptr<Zone>> 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<Zone>(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);
}

24
src/Linker/Linker.h Normal file
View File

@ -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;
};

133
src/Linker/LinkerArgs.cpp Normal file
View File

@ -0,0 +1,133 @@
#include "LinkerArgs.h"
#include <regex>
#include <type_traits>
#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<decltype(COMMAND_LINE_OPTIONS)>::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);
}

38
src/Linker/LinkerArgs.h Normal file
View File

@ -0,0 +1,38 @@
#pragma once
#include <vector>
#include <set>
#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<std::string> m_zones_to_load;
std::vector<std::string> m_zones_to_build;
std::set<std::string> 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;
};

View File

@ -1,4 +1,8 @@
int main(int argc, const char** argv) #include "Linker.h"
int main(const int argc, const char** argv)
{ {
return 0; Linker linker;
}
return linker.Start(argc, argv) ? 0 : 1;
}

View File

@ -3,10 +3,10 @@
#include "Utils/FileUtils.h" #include "Utils/FileUtils.h"
constexpr uint32_t WAV_WAVE_ID = MakeMagic32('W', 'A', 'V', 'E'); constexpr uint32_t WAV_WAVE_ID = FileUtils::MakeMagic32('W', 'A', 'V', 'E');
constexpr uint32_t WAV_CHUNK_ID_RIFF = MakeMagic32('R', 'I', 'F', 'F'); constexpr uint32_t WAV_CHUNK_ID_RIFF = FileUtils::MakeMagic32('R', 'I', 'F', 'F');
constexpr uint32_t WAV_CHUNK_ID_FMT = MakeMagic32('f', 'm', 't', ' '); constexpr uint32_t WAV_CHUNK_ID_FMT = FileUtils::MakeMagic32('f', 'm', 't', ' ');
constexpr uint32_t WAV_CHUNK_ID_DATA = MakeMagic32('d', 'a', 't', 'a'); constexpr uint32_t WAV_CHUNK_ID_DATA = FileUtils::MakeMagic32('d', 'a', 't', 'a');
struct WavChunkHeader struct WavChunkHeader
{ {

View File

@ -18,7 +18,7 @@ ObjContainerRepository<IPak, Zone> IPak::Repository;
class IPak::Impl : public ObjContainerReferenceable 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; static const uint32_t VERSION = 0x50000;
std::string m_path; std::string m_path;

View File

@ -6,6 +6,7 @@
#include "Utils/Arguments/UsageInformation.h" #include "Utils/Arguments/UsageInformation.h"
#include "ObjLoading.h" #include "ObjLoading.h"
#include "ObjWriting.h" #include "ObjWriting.h"
#include "Utils/FileUtils.h"
const CommandLineOption* const OPTION_HELP = const CommandLineOption* const OPTION_HELP =
CommandLineOption::Builder::Create() CommandLineOption::Builder::Create()
@ -84,66 +85,6 @@ UnlinkerArgs::UnlinkerArgs()
m_verbose = false; m_verbose = false;
} }
bool UnlinkerArgs::ParsePathsString(const std::string& pathsString, std::set<std::string>& 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() void UnlinkerArgs::PrintUsage()
{ {
UsageInformation usage("unlinker.exe"); UsageInformation usage("unlinker.exe");
@ -231,7 +172,7 @@ bool UnlinkerArgs::ParseArgs(const int argc, const char** argv)
// --search-path // --search-path
if (m_argument_parser.IsOptionSpecified(OPTION_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; return false;
} }

View File

@ -14,14 +14,6 @@ class UnlinkerArgs
*/ */
static void PrintUsage(); 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<std::string>& output);
void SetVerbose(bool isVerbose); void SetVerbose(bool isVerbose);
bool SetImageDumpingMode(); bool SetImageDumpingMode();

View File

@ -0,0 +1,63 @@
#include "FileUtils.h"
#include <sstream>
bool FileUtils::ParsePathsString(const std::string& pathsString, std::set<std::string>& 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;
}

View File

@ -1,10 +1,24 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <set>
#include <string>
constexpr uint32_t MakeMagic32(const char ch0, const char ch1, const char ch2, const char ch3) class FileUtils
{ {
return static_cast<uint32_t>(ch0) public:
| static_cast<uint32_t>(ch1) << 8 static constexpr uint32_t MakeMagic32(const char ch0, const char ch1, const char ch2, const char ch3)
| static_cast<uint32_t>(ch2) << 16 {
| static_cast<uint32_t>(ch3) << 24; return static_cast<uint32_t>(ch0)
} | static_cast<uint32_t>(ch1) << 8
| static_cast<uint32_t>(ch2) << 16
| static_cast<uint32_t>(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<std::string>& output);
};

View File

@ -0,0 +1,6 @@
#include "ZoneWriting.h"
bool ZoneWriting::WriteZone(Zone* zone)
{
return true;
}

View File

@ -0,0 +1,9 @@
#pragma once
#include <string>
#include "Zone/Zone.h"
class ZoneWriting
{
public:
static bool WriteZone(Zone* zone);
};