2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2025-12-27 20:41:49 +00:00

chore: restructure Linker and Unlinker for system testing

This commit is contained in:
Jan Laupetin
2025-12-23 12:49:22 +01:00
parent fd9c57e15a
commit a1693b2eb8
32 changed files with 1089 additions and 982 deletions

476
src/Linking/Linker.cpp Normal file
View File

@@ -0,0 +1,476 @@
#include "Linker.h"
#include "LinkerArgs.h"
#include "LinkerPaths.h"
#include "ObjContainer/SoundBank/SoundBankWriter.h"
#include "ObjWriting.h"
#include "SearchPath/OutputPathFilesystem.h"
#include "SearchPath/SearchPaths.h"
#include "Utils/Logging/Log.h"
#include "Utils/ObjFileStream.h"
#include "Zone/AssetList/AssetList.h"
#include "Zone/AssetList/AssetListReader.h"
#include "Zone/Definition/ZoneDefinitionStream.h"
#include "ZoneCreation/ZoneCreationContext.h"
#include "ZoneCreation/ZoneCreator.h"
#include "ZoneLoading.h"
#include "ZoneWriting.h"
#include <deque>
#include <filesystem>
#include <format>
#include <fstream>
#include <unordered_set>
namespace fs = std::filesystem;
namespace
{
class LinkerSearchPathContext
{
public:
explicit LinkerSearchPathContext(const ILinkerSearchPathBuilder& searchPathBuilder)
: m_search_path_builder(searchPathBuilder)
{
m_independent_search_paths = m_search_path_builder.BuildIndependentSearchPaths();
if (m_independent_search_paths)
m_search_paths.IncludeSearchPath(m_independent_search_paths.get());
}
[[nodiscard]] ISearchPath& GetSearchPaths()
{
return m_search_paths;
}
void LoadProjectSpecific(const std::string& projectName)
{
m_project_specific_search_paths = m_search_path_builder.BuildSearchPathsSpecificToProject(projectName);
if (m_project_specific_search_paths)
m_search_paths.IncludeSearchPath(m_project_specific_search_paths.get());
}
void UnloadProjectSpecific()
{
if (!m_project_specific_search_paths)
return;
m_search_paths.RemoveSearchPath(m_project_specific_search_paths.get());
m_project_specific_search_paths.reset();
}
void LoadGameSpecific(const std::string& projectName, const GameId game)
{
m_game_specific_search_paths = m_search_path_builder.BuildSearchPathsSpecificToProjectAndGame(projectName, game);
if (m_game_specific_search_paths)
m_search_paths.IncludeSearchPath(m_game_specific_search_paths.get());
}
void UnloadGameSpecific()
{
if (!m_game_specific_search_paths)
return;
m_search_paths.RemoveSearchPath(m_game_specific_search_paths.get());
m_game_specific_search_paths.reset();
}
private:
const ILinkerSearchPathBuilder& m_search_path_builder;
std::unique_ptr<ISearchPath> m_independent_search_paths;
std::unique_ptr<ISearchPath> m_project_specific_search_paths;
std::unique_ptr<ISearchPath> m_game_specific_search_paths;
SearchPaths m_search_paths;
};
class LinkerPathManager
{
public:
explicit LinkerPathManager(const LinkerArgs& args)
: m_linker_paths(ILinkerPaths::FromArgs(args)),
m_asset_paths(m_linker_paths->AssetSearchPaths()),
m_gdt_paths(m_linker_paths->GdtSearchPaths()),
m_source_paths(m_linker_paths->SourceSearchPaths())
{
}
std::unique_ptr<ILinkerPaths> m_linker_paths;
LinkerSearchPathContext m_asset_paths;
LinkerSearchPathContext m_gdt_paths;
LinkerSearchPathContext m_source_paths;
};
class PathProjectContext
{
public:
PathProjectContext(LinkerPathManager& paths, const std::string& projectName)
: m_paths(paths)
{
m_paths.m_asset_paths.LoadProjectSpecific(projectName);
m_paths.m_gdt_paths.LoadProjectSpecific(projectName);
m_paths.m_source_paths.LoadProjectSpecific(projectName);
}
~PathProjectContext()
{
m_paths.m_asset_paths.UnloadProjectSpecific();
m_paths.m_gdt_paths.UnloadProjectSpecific();
m_paths.m_source_paths.UnloadProjectSpecific();
}
PathProjectContext(const PathProjectContext& other) = delete;
PathProjectContext(PathProjectContext&& other) noexcept = delete;
PathProjectContext& operator=(const PathProjectContext& other) = delete;
PathProjectContext& operator=(PathProjectContext&& other) noexcept = delete;
private:
LinkerPathManager& m_paths;
};
class PathGameContext
{
public:
PathGameContext(LinkerPathManager& paths, const std::string& projectName, const GameId game)
: m_paths(paths)
{
m_paths.m_asset_paths.LoadGameSpecific(projectName, game);
m_paths.m_gdt_paths.LoadGameSpecific(projectName, game);
m_paths.m_source_paths.LoadGameSpecific(projectName, game);
}
~PathGameContext()
{
m_paths.m_asset_paths.UnloadGameSpecific();
m_paths.m_gdt_paths.UnloadGameSpecific();
m_paths.m_source_paths.UnloadGameSpecific();
}
PathGameContext(const PathGameContext& other) = delete;
PathGameContext(PathGameContext&& other) noexcept = delete;
PathGameContext& operator=(const PathGameContext& other) = delete;
PathGameContext& operator=(PathGameContext&& other) noexcept = delete;
private:
LinkerPathManager& m_paths;
};
class LinkerImpl final : public Linker
{
public:
LinkerImpl(LinkerArgs args)
: m_args(std::move(args))
{
}
bool Start() override
{
LinkerPathManager paths(m_args);
if (!LoadZones())
return false;
auto result = true;
for (const auto& projectSpecifier : m_args.m_project_specifiers_to_build)
{
std::string projectName;
std::string targetName;
if (!GetProjectAndTargetFromProjectSpecifier(projectSpecifier, projectName, targetName))
{
result = false;
break;
}
if (!BuildProject(paths, projectName, targetName))
{
result = false;
break;
}
}
UnloadZones();
return result;
}
private:
std::unique_ptr<ZoneDefinition> ReadZoneDefinition(LinkerPathManager& paths, const std::string& targetName, bool logMissing = true) const
{
auto& sourceSearchPath = paths.m_source_paths.GetSearchPaths();
std::unique_ptr<ZoneDefinition> zoneDefinition;
{
const auto definitionFileName = std::format("{}.zone", targetName);
const auto definitionStream = sourceSearchPath.Open(definitionFileName);
if (!definitionStream.IsOpen())
{
if (logMissing)
con::error("Could not find zone definition file for target \"{}\".", targetName);
return nullptr;
}
ZoneDefinitionInputStream zoneDefinitionInputStream(*definitionStream.m_stream, targetName, definitionFileName, sourceSearchPath);
zoneDefinition = zoneDefinitionInputStream.ReadDefinition();
}
if (!zoneDefinition)
{
con::error("Failed to read zone definition file for target \"{}\".", targetName);
return nullptr;
}
return zoneDefinition;
}
bool ReadIgnoreEntries(LinkerPathManager& paths, const std::string& zoneName, const GameId game, AssetList& assetList) const
{
{
AssetListReader assetListReader(paths.m_source_paths.GetSearchPaths(), game);
const auto maybeReadAssetList = assetListReader.ReadAssetList(zoneName, false);
if (maybeReadAssetList)
{
assetList.m_entries.reserve(assetList.m_entries.size() + maybeReadAssetList->m_entries.size());
for (auto& entry : maybeReadAssetList->m_entries)
assetList.m_entries.emplace_back(std::move(entry));
return true;
}
}
{
const auto zoneDefinition = ReadZoneDefinition(paths, zoneName, false);
if (zoneDefinition)
{
assetList.m_entries.reserve(assetList.m_entries.size() + zoneDefinition->m_assets.size());
for (const auto& entry : zoneDefinition->m_assets)
assetList.m_entries.emplace_back(entry.m_asset_type, entry.m_asset_name, entry.m_is_reference);
return true;
}
}
return false;
}
bool ProcessZoneDefinitionIgnores(LinkerPathManager& paths, const std::string& targetName, ZoneCreationContext& context) const
{
if (context.m_definition->m_ignores.empty())
return true;
for (const auto& ignore : context.m_definition->m_ignores)
{
if (ignore == targetName)
continue;
if (!ReadIgnoreEntries(paths, ignore, context.m_definition->m_game, context.m_ignored_assets))
{
con::error("Failed to read asset listing for ignoring assets of project \"{}\".", ignore);
return false;
}
}
return true;
}
static bool LoadGdtFilesFromZoneDefinition(std::vector<std::unique_ptr<Gdt>>& gdtList, const ZoneDefinition& zoneDefinition, ISearchPath* gdtSearchPath)
{
for (const auto& gdtName : zoneDefinition.m_gdts)
{
const auto gdtFile = gdtSearchPath->Open(std::format("{}.gdt", gdtName));
if (!gdtFile.IsOpen())
{
con::error("Failed to open file for gdt \"{}\"", gdtName);
return false;
}
GdtReader gdtReader(*gdtFile.m_stream);
auto gdt = std::make_unique<Gdt>();
if (!gdtReader.Read(*gdt))
{
con::error("Failed to read gdt file \"{}\"", gdtName);
return false;
}
gdtList.emplace_back(std::move(gdt));
}
return true;
}
std::unique_ptr<Zone> CreateZoneForDefinition(
LinkerPathManager& paths, const fs::path& outDir, const fs::path& cacheDir, const std::string& targetName, ZoneDefinition& zoneDefinition) const
{
ZoneCreationContext context(&zoneDefinition, &paths.m_asset_paths.GetSearchPaths(), outDir, cacheDir);
if (!ProcessZoneDefinitionIgnores(paths, targetName, context))
return nullptr;
if (!LoadGdtFilesFromZoneDefinition(context.m_gdt_files, zoneDefinition, &paths.m_gdt_paths.GetSearchPaths()))
return nullptr;
return zone_creator::CreateZoneForDefinition(zoneDefinition.m_game, context);
}
static bool WriteZoneToFile(IOutputPath& outPath, const Zone& zone)
{
const auto stream = outPath.Open(std::format("{}.ff", zone.m_name));
if (!stream)
{
con::error("Failed to open file for zone: {}", zone.m_name);
return false;
}
con::info("Building zone \"{}\"", zone.m_name);
if (!ZoneWriting::WriteZone(*stream, zone))
{
con::error("Writing zone failed.");
return false;
}
con::info("Created zone \"{}\"", zone.m_name);
return true;
}
bool BuildFastFile(LinkerPathManager& paths, const std::string& projectName, const std::string& targetName, ZoneDefinition& zoneDefinition) const
{
const fs::path outDir(paths.m_linker_paths->BuildOutputFolderPath(projectName, zoneDefinition.m_game));
OutputPathFilesystem outputPath(outDir);
const fs::path cacheDir(paths.m_linker_paths->BuildCacheFolderPath(projectName, zoneDefinition.m_game));
SoundBankWriter::OutputPath = outDir;
const auto zone = CreateZoneForDefinition(paths, outDir, cacheDir, targetName, zoneDefinition);
auto result = zone != nullptr;
if (zone)
result = WriteZoneToFile(outputPath, *zone);
return result;
}
bool BuildProject(LinkerPathManager& paths, const std::string& projectName, const std::string& targetName) const
{
std::deque<std::string> targetsToBuild;
std::unordered_set<std::string> alreadyBuiltTargets;
targetsToBuild.emplace_back(targetName);
while (!targetsToBuild.empty())
{
const auto currentTarget = std::move(targetsToBuild.front());
targetsToBuild.pop_front();
alreadyBuiltTargets.emplace(currentTarget);
PathProjectContext projectContext(paths, projectName);
const auto zoneDefinition = ReadZoneDefinition(paths, targetName);
if (!zoneDefinition)
return false;
PathGameContext gameContext(paths, projectName, zoneDefinition->m_game);
if (!zoneDefinition->m_assets.empty())
{
if (!BuildFastFile(paths, projectName, targetName, *zoneDefinition))
return false;
for (const auto& referencedTarget : zoneDefinition->m_targets_to_build)
{
if (alreadyBuiltTargets.find(referencedTarget) == alreadyBuiltTargets.end())
{
targetsToBuild.emplace_back(referencedTarget);
con::info("Building referenced target \"{}\"", referencedTarget);
}
}
}
}
return true;
}
bool LoadZones()
{
for (const auto& zonePath : m_args.m_zones_to_load)
{
if (!fs::is_regular_file(zonePath))
{
con::error("Could not find zone file to load \"{}\".", zonePath);
return false;
}
auto zoneDirectory = fs::path(zonePath).remove_filename();
if (zoneDirectory.empty())
zoneDirectory = fs::current_path();
auto absoluteZoneDirectory = absolute(zoneDirectory).string();
auto maybeZone = ZoneLoading::LoadZone(zonePath, std::nullopt);
if (!maybeZone)
{
con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error());
return false;
}
auto zone = std::move(*maybeZone);
con::debug("Loaded zone \"{}\"", zone->m_name);
m_loaded_zones.emplace_back(std::move(zone));
}
return true;
}
void UnloadZones()
{
for (auto i = m_loaded_zones.rbegin(); i != m_loaded_zones.rend(); ++i)
{
auto& loadedZone = *i;
std::string zoneName = loadedZone->m_name;
loadedZone.reset();
con::debug("Unloaded zone \"{}\"", zoneName);
}
m_loaded_zones.clear();
}
static bool GetProjectAndTargetFromProjectSpecifier(const std::string& projectSpecifier, std::string& projectName, std::string& targetName)
{
const auto targetNameSeparatorIndex = projectSpecifier.find_first_of('/');
if (targetNameSeparatorIndex == std::string::npos)
{
projectName = projectSpecifier;
targetName = projectSpecifier;
}
else if (projectSpecifier.find_first_of('/', targetNameSeparatorIndex + 1) != std::string::npos)
{
con::error("Project specifier cannot have more than one target name: \"{}\"", projectSpecifier);
return false;
}
else
{
projectName = projectSpecifier.substr(0, targetNameSeparatorIndex);
targetName = projectSpecifier.substr(targetNameSeparatorIndex + 1);
}
if (projectName.empty())
{
con::error("Project name cannot be empty: \"{}\"", projectSpecifier);
return false;
}
if (targetName.empty())
{
con::error("Target name cannot be empty: \"{}\"", projectSpecifier);
return false;
}
return true;
}
LinkerArgs m_args;
std::vector<std::unique_ptr<Zone>> m_loaded_zones;
};
} // namespace
std::unique_ptr<Linker> Linker::Create(LinkerArgs args)
{
return std::make_unique<LinkerImpl>(std::move(args));
}

21
src/Linking/Linker.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include "LinkerArgs.h"
#include <memory>
class Linker
{
public:
virtual ~Linker() = default;
static std::unique_ptr<Linker> Create(LinkerArgs args);
/**
* \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.
*/
virtual bool Start() = 0;
};

288
src/Linking/LinkerArgs.cpp Normal file
View File

@@ -0,0 +1,288 @@
#include "LinkerArgs.h"
#include "GitVersion.h"
#include "ObjLoading.h"
#include "ObjWriting.h"
#include "Utils/Arguments/UsageInformation.h"
#include "Utils/FileUtils.h"
#include "Utils/Logging/Log.h"
#include "Utils/PathUtils.h"
#include <filesystem>
#include <format>
#include <iostream>
#include <type_traits>
namespace fs = std::filesystem;
// clang-format off
const CommandLineOption* const OPTION_HELP =
CommandLineOption::Builder::Create()
.WithShortName("?")
.WithLongName("help")
.WithDescription("Displays usage information.")
.Build();
const CommandLineOption* const OPTION_VERSION =
CommandLineOption::Builder::Create()
.WithLongName("version")
.WithDescription("Prints the application version.")
.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_NO_COLOR =
CommandLineOption::Builder::Create()
.WithLongName("no-color")
.WithDescription("Disables colored terminal output.")
.Build();
const CommandLineOption* const OPTION_BASE_FOLDER =
CommandLineOption::Builder::Create()
.WithShortName("b")
.WithLongName("base-folder")
.WithDescription("Specifies the base folder that can be used to specify other folders. Defaults to the current directory.")
.WithParameter("baseFolderPath")
.Build();
const CommandLineOption* const OPTION_OUTPUT_FOLDER =
CommandLineOption::Builder::Create()
.WithLongName("output-folder")
.WithDescription(std::format("Specifies the output folder containing the build artifacts. Defaults to \"{}\".", LinkerArgs::DEFAULT_OUTPUT_FOLDER))
.WithParameter("outputFolderPath")
.Build();
const CommandLineOption* const OPTION_ADD_ASSET_SEARCH_PATH =
CommandLineOption::Builder::Create()
.WithLongName("add-asset-search-path")
.WithDescription("Adds a search paths used for assets. This does not override the default search paths.")
.WithParameter("assetSearchPathString")
.Reusable()
.Build();
const CommandLineOption* const OPTION_ASSET_SEARCH_PATH =
CommandLineOption::Builder::Create()
.WithLongName("asset-search-path")
.WithDescription(std::format("Specifies the search paths used for assets. Defaults to \"{}\".", LinkerArgs::DEFAULT_ASSET_SEARCH_PATH))
.WithParameter("assetSearchPathString")
.Build();
const CommandLineOption* const OPTION_GDT_SEARCH_PATH =
CommandLineOption::Builder::Create()
.WithLongName("gdt-search-path")
.WithDescription(std::format("Specifies the search paths used for gdt files. Defaults to \"{}\".", LinkerArgs::DEFAULT_GDT_SEARCH_PATH))
.WithParameter("gdtSearchPathString")
.Build();
const CommandLineOption* const OPTION_ADD_SOURCE_SEARCH_PATH =
CommandLineOption::Builder::Create()
.WithLongName("add-source-search-path")
.WithDescription("Adds a search paths used for source files. This does not override the default search paths.")
.WithParameter("sourceSearchPathString")
.Reusable()
.Build();
const CommandLineOption* const OPTION_SOURCE_SEARCH_PATH =
CommandLineOption::Builder::Create()
.WithLongName("source-search-path")
.WithDescription(std::format("Specifies the search paths used for source files. Defaults to \"{}\".", LinkerArgs::DEFAULT_SOURCE_SEARCH_PATH))
.WithParameter("sourceSearchPathString")
.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 OPTION_MENU_PERMISSIVE =
CommandLineOption::Builder::Create()
.WithLongName("menu-permissive")
.WithDescription("Allows the usage of unknown script commands that can be compiled.")
.Build();
const CommandLineOption* const OPTION_MENU_NO_OPTIMIZATION =
CommandLineOption::Builder::Create()
.WithLongName("menu-no-optimization")
.WithDescription("Refrain from applying optimizations to parsed menus. (Optimizations increase menu performance and size. May result in less source "
"information when dumped though.)")
.Build();
// clang-format on
const CommandLineOption* const COMMAND_LINE_OPTIONS[]{
OPTION_HELP,
OPTION_VERSION,
OPTION_VERBOSE,
OPTION_NO_COLOR,
OPTION_BASE_FOLDER,
OPTION_OUTPUT_FOLDER,
OPTION_ADD_ASSET_SEARCH_PATH,
OPTION_ASSET_SEARCH_PATH,
OPTION_GDT_SEARCH_PATH,
OPTION_ADD_SOURCE_SEARCH_PATH,
OPTION_SOURCE_SEARCH_PATH,
OPTION_LOAD,
OPTION_MENU_PERMISSIVE,
OPTION_MENU_NO_OPTIMIZATION,
};
LinkerArgs::LinkerArgs()
: m_argument_parser(COMMAND_LINE_OPTIONS, std::extent_v<decltype(COMMAND_LINE_OPTIONS)>)
{
}
void LinkerArgs::PrintUsage() const
{
UsageInformation usage(m_argument_parser.GetExecutableName());
for (const auto* commandLineOption : COMMAND_LINE_OPTIONS)
{
usage.AddCommandLineOption(commandLineOption);
}
usage.AddArgument("projectName");
usage.SetVariableArguments(true);
usage.Print();
}
void LinkerArgs::PrintVersion()
{
con::info("OpenAssetTools Linker {}", GIT_VERSION);
}
void LinkerArgs::SetBinFolder()
{
const fs::path path(utils::GetExecutablePath());
m_bin_folder = path.parent_path().string();
}
bool LinkerArgs::ParseArgs(const int argc, const char** argv, bool& shouldContinue)
{
shouldContinue = true;
if (!m_argument_parser.ParseArguments(argc, argv))
{
PrintUsage();
return false;
}
// Check if the user requested help
if (m_argument_parser.IsOptionSpecified(OPTION_HELP))
{
PrintUsage();
shouldContinue = false;
return true;
}
// Check if the user wants to see the version
if (m_argument_parser.IsOptionSpecified(OPTION_VERSION))
{
PrintVersion();
shouldContinue = false;
return true;
}
SetBinFolder();
m_project_specifiers_to_build = m_argument_parser.GetArguments();
if (m_project_specifiers_to_build.empty())
{
// No projects to build specified...
PrintUsage();
return false;
}
// -v; --verbose
if (m_argument_parser.IsOptionSpecified(OPTION_VERBOSE))
con::set_log_level(con::LogLevel::DEBUG);
else
con::set_log_level(con::LogLevel::INFO);
// --no-color
con::set_use_color(!m_argument_parser.IsOptionSpecified(OPTION_NO_COLOR));
// b; --base-folder
if (m_argument_parser.IsOptionSpecified(OPTION_BASE_FOLDER))
m_base_folder = m_argument_parser.GetValueForOption(OPTION_BASE_FOLDER);
else
m_base_folder = DEFAULT_BASE_FOLDER;
// --output-folder
if (m_argument_parser.IsOptionSpecified(OPTION_OUTPUT_FOLDER))
m_out_folder = m_argument_parser.GetValueForOption(OPTION_OUTPUT_FOLDER);
else
m_out_folder = DEFAULT_OUTPUT_FOLDER;
// --asset-search-path
if (m_argument_parser.IsOptionSpecified(OPTION_ASSET_SEARCH_PATH))
{
if (!FileUtils::ParsePathsString(m_argument_parser.GetValueForOption(OPTION_ASSET_SEARCH_PATH), m_asset_search_paths))
return false;
}
else
{
if (!FileUtils::ParsePathsString(DEFAULT_ASSET_SEARCH_PATH, m_asset_search_paths))
return false;
}
// --add-assets-search-path
for (const auto& specifiedValue : m_argument_parser.GetParametersForOption(OPTION_ADD_ASSET_SEARCH_PATH))
{
if (!FileUtils::ParsePathsString(specifiedValue, m_asset_search_paths))
return false;
}
// --gdt-search-path
if (m_argument_parser.IsOptionSpecified(OPTION_GDT_SEARCH_PATH))
{
if (!FileUtils::ParsePathsString(m_argument_parser.GetValueForOption(OPTION_GDT_SEARCH_PATH), m_gdt_search_paths))
return false;
}
else
{
if (!FileUtils::ParsePathsString(DEFAULT_GDT_SEARCH_PATH, m_gdt_search_paths))
return false;
}
// --source-search-path
if (m_argument_parser.IsOptionSpecified(OPTION_SOURCE_SEARCH_PATH))
{
if (!FileUtils::ParsePathsString(m_argument_parser.GetValueForOption(OPTION_SOURCE_SEARCH_PATH), m_source_search_paths))
return false;
}
else
{
if (!FileUtils::ParsePathsString(DEFAULT_SOURCE_SEARCH_PATH, m_source_search_paths))
return false;
}
// --add-source-search-path
for (const auto& specifiedValue : m_argument_parser.GetParametersForOption(OPTION_ADD_SOURCE_SEARCH_PATH))
{
if (!FileUtils::ParsePathsString(specifiedValue, m_source_search_paths))
return false;
}
// -l; --load
if (m_argument_parser.IsOptionSpecified(OPTION_LOAD))
m_zones_to_load = m_argument_parser.GetParametersForOption(OPTION_LOAD);
// --menu-permissive
if (m_argument_parser.IsOptionSpecified(OPTION_MENU_PERMISSIVE))
ObjLoading::Configuration.MenuPermissiveParsing = true;
// --menu-no-optimization
if (m_argument_parser.IsOptionSpecified(OPTION_MENU_NO_OPTIMIZATION))
ObjLoading::Configuration.MenuNoOptimization = true;
return true;
}

41
src/Linking/LinkerArgs.h Normal file
View File

@@ -0,0 +1,41 @@
#pragma once
#include "Utils/Arguments/ArgumentParser.h"
#include <set>
#include <vector>
class LinkerArgs
{
public:
static constexpr auto DEFAULT_BASE_FOLDER = ".";
static constexpr auto DEFAULT_CACHE_FOLDER = "?base?/.oat/cache/?project?";
static constexpr auto DEFAULT_OUTPUT_FOLDER = "?base?/zone_out/?project?";
static constexpr auto DEFAULT_ASSET_SEARCH_PATH = "?bin?/raw/?game?;?base?/raw;?base?/raw/?game?;?base?/zone_raw/?project?";
static constexpr auto DEFAULT_GDT_SEARCH_PATH = "?base?/source_data;?base?/zone_raw/?project?/source_data";
static constexpr auto DEFAULT_SOURCE_SEARCH_PATH = "?base?/zone_source;?base?/zone_raw/?project?;?base?/zone_raw/?project?/zone_source";
LinkerArgs();
bool ParseArgs(int argc, const char** argv, bool& shouldContinue);
std::vector<std::string> m_zones_to_load;
std::vector<std::string> m_project_specifiers_to_build;
std::string m_bin_folder;
std::string m_base_folder;
std::string m_out_folder;
std::set<std::string> m_asset_search_paths;
std::set<std::string> m_gdt_search_paths;
std::set<std::string> m_source_search_paths;
private:
/**
* \brief Prints a command line usage help text for the Linker tool to stdout.
*/
void PrintUsage() const;
static void PrintVersion();
void SetBinFolder();
ArgumentParser m_argument_parser;
};

343
src/Linking/LinkerPaths.cpp Normal file
View File

@@ -0,0 +1,343 @@
#include "LinkerPaths.h"
#include "SearchPath/IWD.h"
#include "SearchPath/SearchPathFilesystem.h"
#include "SearchPath/SearchPaths.h"
#include "Utils/Logging/Log.h"
#include "Utils/StringUtils.h"
#include <cassert>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <format>
#include <iostream>
#include <sstream>
#include <type_traits>
#include <unordered_set>
namespace fs = std::filesystem;
namespace
{
enum class PathTemplateParameterType : std::uint8_t
{
BIN = 1 << 0,
BASE = 1 << 1,
PROJECT = 1 << 2,
GAME = 1 << 3,
};
class LinkerPathTemplate
{
static constexpr auto PATTERN_BIN = "?bin?";
static constexpr auto PATTERN_BASE = "?base?";
static constexpr auto PATTERN_GAME = "?game?";
static constexpr auto PATTERN_PROJECT = "?project?";
struct Pattern
{
const char* m_str;
PathTemplateParameterType m_type;
};
static constexpr Pattern PATTERNS[]{
{PATTERN_BIN, PathTemplateParameterType::BIN },
{PATTERN_BASE, PathTemplateParameterType::BASE },
{PATTERN_GAME, PathTemplateParameterType::GAME },
{PATTERN_PROJECT, PathTemplateParameterType::PROJECT},
};
public:
LinkerPathTemplate()
: m_parameter_type_flags(0u)
{
}
void CreateFromString(const std::string& templateString)
{
const auto templateStringLength = templateString.size();
auto partStart = 0uz;
for (auto i = 0uz; i < templateStringLength; i++)
{
if (templateString[i] != '?')
continue;
for (const auto& pattern : PATTERNS)
{
const auto patternLength = std::strlen(pattern.m_str);
if (templateString.compare(i, patternLength, pattern.m_str) == 0)
{
m_parts.emplace_back(templateString.substr(partStart, i - partStart));
m_parameters.emplace_back(pattern.m_type);
m_parameter_type_flags |= static_cast<std::underlying_type_t<PathTemplateParameterType>>(pattern.m_type);
i += patternLength;
partStart = i;
break;
}
}
}
if (partStart < templateStringLength)
m_parts.emplace_back(templateString.substr(partStart, templateStringLength - partStart));
}
[[nodiscard]] std::string
Render(const std::string& binDir, const std::string& baseDir, const std::string& projectName, const std::string& gameName) const
{
if (m_parts.empty())
return "";
if (m_parameters.empty())
return m_parts[0];
std::ostringstream ss;
ss << m_parts[0];
const auto partsCount = m_parts.size();
const auto parameterCount = m_parameters.size();
for (auto parameterIndex = 0u; parameterIndex < parameterCount; parameterIndex++)
{
switch (m_parameters[parameterIndex])
{
case PathTemplateParameterType::BIN:
ss << binDir;
break;
case PathTemplateParameterType::BASE:
ss << baseDir;
break;
case PathTemplateParameterType::PROJECT:
ss << projectName;
break;
case PathTemplateParameterType::GAME:
ss << gameName;
break;
default:
assert(false);
break;
}
if (parameterIndex + 1 < partsCount)
ss << m_parts[parameterIndex + 1];
}
return fs::path(ss.str()).make_preferred().string();
}
[[nodiscard]] bool CanRender(const std::underlying_type_t<PathTemplateParameterType> availableParameters) const
{
return (m_parameter_type_flags & ~availableParameters) == 0;
}
private:
std::vector<std::string> m_parts;
std::vector<PathTemplateParameterType> m_parameters;
std::underlying_type_t<PathTemplateParameterType> m_parameter_type_flags;
};
class LinkerSearchPathBuilder final : public ILinkerSearchPathBuilder
{
static constexpr auto INDEPENDENT_MASK = static_cast<unsigned>(PathTemplateParameterType::BIN) | static_cast<unsigned>(PathTemplateParameterType::BASE);
static constexpr auto PROJECT_MASK = static_cast<unsigned>(PathTemplateParameterType::BIN) | static_cast<unsigned>(PathTemplateParameterType::BASE)
| static_cast<unsigned>(PathTemplateParameterType::PROJECT);
static constexpr auto GAME_MASK = static_cast<unsigned>(PathTemplateParameterType::BIN) | static_cast<unsigned>(PathTemplateParameterType::BASE)
| static_cast<unsigned>(PathTemplateParameterType::PROJECT) | static_cast<unsigned>(PathTemplateParameterType::GAME);
public:
LinkerSearchPathBuilder(const char* typeName, std::string binDir, std::string baseDir)
: m_type_name(typeName),
m_bin_dir(std::move(binDir)),
m_base_dir(std::move(baseDir))
{
}
void BuildFromArgs(const std::set<std::string>& templates)
{
m_templates.reserve(templates.size());
for (const auto& templateString : templates)
{
LinkerPathTemplate templateStruct;
templateStruct.CreateFromString(templateString);
m_templates.emplace_back(std::move(templateStruct));
}
}
[[nodiscard]] std::unique_ptr<ISearchPath> BuildIndependentSearchPaths() const override
{
SearchPaths searchPaths;
std::unordered_set<std::string> addedSearchPaths;
auto hasSearchPath = false;
for (const auto& curTemplate : m_templates)
{
if (curTemplate.CanRender(INDEPENDENT_MASK))
{
auto renderedTemplate = curTemplate.Render(m_bin_dir, m_base_dir, std::string(), std::string());
if (AddSearchPath(addedSearchPaths, searchPaths, renderedTemplate))
hasSearchPath = true;
}
}
if (hasSearchPath)
return std::make_unique<SearchPaths>(std::move(searchPaths));
return {};
}
[[nodiscard]] std::unique_ptr<ISearchPath> BuildSearchPathsSpecificToProject(const std::string& projectName) const override
{
SearchPaths searchPaths;
std::unordered_set<std::string> addedSearchPaths;
auto hasSearchPath = false;
for (const auto& curTemplate : m_templates)
{
if (!curTemplate.CanRender(INDEPENDENT_MASK) && curTemplate.CanRender(PROJECT_MASK))
{
auto renderedTemplate = curTemplate.Render(m_bin_dir, m_base_dir, projectName, std::string());
if (AddSearchPath(addedSearchPaths, searchPaths, renderedTemplate))
hasSearchPath = true;
}
}
if (hasSearchPath)
return std::make_unique<SearchPaths>(std::move(searchPaths));
return {};
}
[[nodiscard]] std::unique_ptr<ISearchPath> BuildSearchPathsSpecificToProjectAndGame(const std::string& projectName, GameId game) const override
{
SearchPaths searchPaths;
std::unordered_set<std::string> addedSearchPaths;
auto hasSearchPath = false;
for (const auto& curTemplate : m_templates)
{
if (!curTemplate.CanRender(PROJECT_MASK) && curTemplate.CanRender(GAME_MASK))
{
std::string gameName(GameId_Names[static_cast<unsigned>(game)]);
utils::MakeStringLowerCase(gameName);
auto renderedTemplate = curTemplate.Render(m_bin_dir, m_base_dir, projectName, gameName);
if (AddSearchPath(addedSearchPaths, searchPaths, renderedTemplate))
hasSearchPath = true;
}
}
if (hasSearchPath)
return std::make_unique<SearchPaths>(std::move(searchPaths));
return {};
}
private:
bool AddSearchPath(std::unordered_set<std::string>& existingSearchPaths, SearchPaths& searchPaths, const std::string& path) const
{
const auto existingSearchPath = existingSearchPaths.find(path);
if (existingSearchPath != existingSearchPaths.end())
return false;
existingSearchPaths.emplace(path);
if (!fs::is_directory(path))
{
con::debug("Adding {} search path (Not found): {}", m_type_name, path);
return false;
}
con::debug("Adding {} search path: {}", m_type_name, path);
searchPaths.CommitSearchPath(std::make_unique<SearchPathFilesystem>(path));
return true;
}
const char* m_type_name;
std::vector<LinkerPathTemplate> m_templates;
std::string m_bin_dir;
std::string m_base_dir;
};
class LinkerPaths final : public ILinkerPaths
{
public:
LinkerPaths(std::string binDir,
std::string baseDir,
LinkerSearchPathBuilder assetSearchPaths,
LinkerSearchPathBuilder gdtSearchPaths,
LinkerSearchPathBuilder sourceSearchPaths,
LinkerPathTemplate cacheTemplate,
LinkerPathTemplate outTemplate)
: m_bin_dir(std::move(binDir)),
m_base_dir(std::move(baseDir)),
m_asset_search_paths(std::move(assetSearchPaths)),
m_gdt_search_paths(std::move(gdtSearchPaths)),
m_source_search_paths(std::move(sourceSearchPaths)),
m_cache_template(std::move(cacheTemplate)),
m_out_template(std::move(outTemplate))
{
}
[[nodiscard]] const ILinkerSearchPathBuilder& AssetSearchPaths() const override
{
return m_asset_search_paths;
}
[[nodiscard]] const ILinkerSearchPathBuilder& GdtSearchPaths() const override
{
return m_gdt_search_paths;
}
[[nodiscard]] const ILinkerSearchPathBuilder& SourceSearchPaths() const override
{
return m_source_search_paths;
}
[[nodiscard]] std::string BuildCacheFolderPath(const std::string& projectName, GameId game) const override
{
return m_cache_template.Render(m_bin_dir, m_base_dir, projectName, GameId_Names[static_cast<unsigned>(game)]);
}
[[nodiscard]] std::string BuildOutputFolderPath(const std::string& projectName, GameId game) const override
{
return m_out_template.Render(m_bin_dir, m_base_dir, projectName, GameId_Names[static_cast<unsigned>(game)]);
}
private:
std::string m_bin_dir;
std::string m_base_dir;
LinkerSearchPathBuilder m_asset_search_paths;
LinkerSearchPathBuilder m_gdt_search_paths;
LinkerSearchPathBuilder m_source_search_paths;
LinkerPathTemplate m_cache_template;
LinkerPathTemplate m_out_template;
};
} // namespace
std::unique_ptr<ILinkerPaths> ILinkerPaths::FromArgs(const LinkerArgs& args)
{
std::string normalizedBinPath = fs::weakly_canonical(args.m_bin_folder).make_preferred().string();
std::string normalizedBasePath = fs::weakly_canonical(args.m_base_folder).make_preferred().string();
LinkerSearchPathBuilder assetSearchPaths("asset", normalizedBinPath, normalizedBasePath);
assetSearchPaths.BuildFromArgs(args.m_asset_search_paths);
LinkerSearchPathBuilder gdtSearchPaths("gdt", normalizedBinPath, normalizedBasePath);
gdtSearchPaths.BuildFromArgs(args.m_gdt_search_paths);
LinkerSearchPathBuilder sourceSearchPaths("source", normalizedBinPath, normalizedBasePath);
sourceSearchPaths.BuildFromArgs(args.m_source_search_paths);
LinkerPathTemplate cacheTemplate;
cacheTemplate.CreateFromString(LinkerArgs::DEFAULT_CACHE_FOLDER);
LinkerPathTemplate outTemplate;
outTemplate.CreateFromString(args.m_out_folder);
return std::make_unique<LinkerPaths>(std::move(normalizedBinPath),
std::move(normalizedBasePath),
std::move(assetSearchPaths),
std::move(gdtSearchPaths),
std::move(sourceSearchPaths),
std::move(cacheTemplate),
std::move(outTemplate));
}

74
src/Linking/LinkerPaths.h Normal file
View File

@@ -0,0 +1,74 @@
#pragma once
#include "Game/IGame.h"
#include "LinkerArgs.h"
#include "SearchPath/ISearchPath.h"
#include <memory>
class ILinkerSearchPathBuilder
{
public:
ILinkerSearchPathBuilder() = default;
virtual ~ILinkerSearchPathBuilder() = default;
ILinkerSearchPathBuilder(const ILinkerSearchPathBuilder& other) = default;
ILinkerSearchPathBuilder(ILinkerSearchPathBuilder&& other) noexcept = default;
ILinkerSearchPathBuilder& operator=(const ILinkerSearchPathBuilder& other) = default;
ILinkerSearchPathBuilder& operator=(ILinkerSearchPathBuilder&& other) noexcept = default;
[[nodiscard]] virtual std::unique_ptr<ISearchPath> BuildIndependentSearchPaths() const = 0;
[[nodiscard]] virtual std::unique_ptr<ISearchPath> BuildSearchPathsSpecificToProject(const std::string& projectName) const = 0;
[[nodiscard]] virtual std::unique_ptr<ISearchPath> BuildSearchPathsSpecificToProjectAndGame(const std::string& projectName, GameId game) const = 0;
};
class ILinkerPaths
{
public:
ILinkerPaths() = default;
virtual ~ILinkerPaths() = default;
ILinkerPaths(const ILinkerPaths& other) = default;
ILinkerPaths(ILinkerPaths&& other) noexcept = default;
ILinkerPaths& operator=(const ILinkerPaths& other) = default;
ILinkerPaths& operator=(ILinkerPaths&& other) noexcept = default;
/**
* \brief Creates linker search paths based on templates from user specified args.
* \param args The user specified args.
* \return Linker search paths based on user specified templates.
*/
static std::unique_ptr<ILinkerPaths> FromArgs(const LinkerArgs& args);
/**
* \brief Grants access to the builder for asset search paths.
* \return A builder instance for building asset search paths.
*/
[[nodiscard]] virtual const ILinkerSearchPathBuilder& AssetSearchPaths() const = 0;
/**
* \brief Grants access to the builder for gdt search paths.
* \return A builder instance for building gdt search paths.
*/
[[nodiscard]] virtual const ILinkerSearchPathBuilder& GdtSearchPaths() const = 0;
/**
* \brief Grants access to the builder for source search paths.
* \return A builder instance for building source search paths.
*/
[[nodiscard]] virtual const ILinkerSearchPathBuilder& SourceSearchPaths() const = 0;
/**
* \brief Builds the cache path based on the specified information.
* \param projectName The name of the project to resolve the path input for.
* \param game The game to resolve the path input for.
* \return A cache path based on the input and preconfigured template.
*/
[[nodiscard]] virtual std::string BuildCacheFolderPath(const std::string& projectName, GameId game) const = 0;
/**
* \brief Builds the output path based on the specified information.
* \param projectName The name of the project to resolve the path input for.
* \param game The game to resolve the path input for.
* \return An output path based on the input and preconfigured template.
*/
[[nodiscard]] virtual std::string BuildOutputFolderPath(const std::string& projectName, GameId game) const = 0;
};

View File

@@ -0,0 +1,17 @@
#include "ZoneCreationContext.h"
namespace fs = std::filesystem;
ZoneCreationContext::ZoneCreationContext()
: m_definition(nullptr),
m_asset_search_path(nullptr)
{
}
ZoneCreationContext::ZoneCreationContext(ZoneDefinition* definition, ISearchPath* assetSearchPath, fs::path outDir, fs::path cacheDir)
: m_definition(definition),
m_asset_search_path(assetSearchPath),
m_out_dir(std::move(outDir)),
m_cache_dir(std::move(cacheDir))
{
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include "Obj/Gdt/Gdt.h"
#include "SearchPath/ISearchPath.h"
#include "Zone/AssetList/AssetList.h"
#include "Zone/Definition/ZoneDefinition.h"
#include <filesystem>
#include <memory>
#include <vector>
class ZoneCreationContext
{
public:
ZoneDefinition* m_definition;
ISearchPath* m_asset_search_path;
std::filesystem::path m_out_dir;
std::filesystem::path m_cache_dir;
std::vector<std::unique_ptr<Gdt>> m_gdt_files;
AssetList m_ignored_assets;
ZoneCreationContext();
ZoneCreationContext(ZoneDefinition* definition, ISearchPath* assetSearchPath, std::filesystem::path outDir, std::filesystem::path cacheDir);
};

View File

@@ -0,0 +1,91 @@
#include "ZoneCreator.h"
#include "Gdt/GdtLookup.h"
#include "IObjCompiler.h"
#include "IObjLoader.h"
#include "SearchPath/OutputPathFilesystem.h"
#include <cassert>
namespace
{
std::unique_ptr<Zone> CreateZone(const ZoneCreationContext& context, const GameId gameId)
{
return std::make_unique<Zone>(context.m_definition->m_name, 0, gameId, GamePlatform::PC);
}
std::vector<Gdt*> CreateGdtList(const ZoneCreationContext& context)
{
std::vector<Gdt*> gdtList;
gdtList.reserve(context.m_gdt_files.size());
for (const auto& gdt : context.m_gdt_files)
gdtList.push_back(gdt.get());
return gdtList;
}
void IgnoreReferencesFromAssets(ZoneCreationContext& context)
{
for (const auto& assetEntry : context.m_definition->m_assets)
{
if (!assetEntry.m_is_reference)
continue;
context.m_ignored_assets.m_entries.emplace_back(assetEntry.m_asset_type, assetEntry.m_asset_name, assetEntry.m_is_reference);
}
}
} // namespace
namespace zone_creator
{
void InitLookup(const ZoneCreationContext& context, GdtLookup& lookup)
{
std::vector<const Gdt*> gdtFiles;
gdtFiles.reserve(context.m_gdt_files.size());
for (const auto& gdt : context.m_gdt_files)
{
gdtFiles.emplace_back(gdt.get());
}
lookup.Initialize(gdtFiles);
}
std::unique_ptr<Zone> CreateZoneForDefinition(GameId gameId, ZoneCreationContext& context)
{
auto zone = CreateZone(context, gameId);
IgnoreReferencesFromAssets(context);
IgnoredAssetLookup ignoredAssetLookup(context.m_ignored_assets);
GdtLookup lookup;
InitLookup(context, lookup);
const auto* objCompiler = IObjCompiler::GetObjCompilerForGame(gameId);
const auto* objLoader = IObjLoader::GetObjLoaderForGame(gameId);
AssetCreatorCollection creatorCollection(*zone);
ZoneDefinitionContext zoneDefinitionContext(*context.m_definition);
AssetCreationContext creationContext(*zone, &creatorCollection, &ignoredAssetLookup);
OutputPathFilesystem outDir(context.m_out_dir);
OutputPathFilesystem cacheDir(context.m_cache_dir);
objCompiler->ConfigureCreatorCollection(
creatorCollection, *zone, zoneDefinitionContext, *context.m_asset_search_path, lookup, creationContext, outDir, cacheDir);
objLoader->ConfigureCreatorCollection(creatorCollection, *zone, *context.m_asset_search_path, lookup);
for (const auto& assetEntry : context.m_definition->m_assets)
{
const auto* createdAsset = creationContext.LoadDependencyGeneric(assetEntry.m_asset_type, assetEntry.m_asset_name);
if (!createdAsset)
return nullptr;
++zoneDefinitionContext.m_asset_index_in_definition;
}
creatorCollection.FinalizeZone(creationContext);
return zone;
}
} // namespace zone_creator

View File

@@ -0,0 +1,9 @@
#pragma once
#include "Zone/Zone.h"
#include "ZoneCreationContext.h"
namespace zone_creator
{
[[nodiscard]] std::unique_ptr<Zone> CreateZoneForDefinition(GameId game, ZoneCreationContext& context);
}