mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2025-12-27 12:31:50 +00:00
chore: restructure Linker and Unlinker for system testing
This commit is contained in:
12
premake5.lua
12
premake5.lua
@@ -129,11 +129,13 @@ group ""
|
|||||||
include "src/Common.lua"
|
include "src/Common.lua"
|
||||||
include "src/Cryptography.lua"
|
include "src/Cryptography.lua"
|
||||||
include "src/ImageConverter.lua"
|
include "src/ImageConverter.lua"
|
||||||
include "src/Linker.lua"
|
include "src/LinkerCli.lua"
|
||||||
|
include "src/Linking.lua"
|
||||||
include "src/ModMan.lua"
|
include "src/ModMan.lua"
|
||||||
include "src/Parser.lua"
|
include "src/Parser.lua"
|
||||||
include "src/RawTemplater.lua"
|
include "src/RawTemplater.lua"
|
||||||
include "src/Unlinker.lua"
|
include "src/UnlinkerCli.lua"
|
||||||
|
include "src/Unlinking.lua"
|
||||||
include "src/Utils.lua"
|
include "src/Utils.lua"
|
||||||
include "src/ZoneCode.lua"
|
include "src/ZoneCode.lua"
|
||||||
include "src/ZoneCodeGeneratorLib.lua"
|
include "src/ZoneCodeGeneratorLib.lua"
|
||||||
@@ -165,6 +167,8 @@ group "Components"
|
|||||||
ObjImage:project()
|
ObjImage:project()
|
||||||
ObjLoading:project()
|
ObjLoading:project()
|
||||||
ObjWriting:project()
|
ObjWriting:project()
|
||||||
|
Linking:project()
|
||||||
|
Unlinking:project()
|
||||||
group ""
|
group ""
|
||||||
|
|
||||||
-- Tools group: All projects that compile into the final tools
|
-- Tools group: All projects that compile into the final tools
|
||||||
@@ -175,8 +179,8 @@ group ""
|
|||||||
|
|
||||||
-- Tools group: All projects that compile into the final tools
|
-- Tools group: All projects that compile into the final tools
|
||||||
group "Tools"
|
group "Tools"
|
||||||
Linker:project()
|
LinkerCli:project()
|
||||||
Unlinker:project()
|
UnlinkerCli:project()
|
||||||
ImageConverter:project()
|
ImageConverter:project()
|
||||||
|
|
||||||
if _OPTIONS["modman"] then
|
if _OPTIONS["modman"] then
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
Linker = {}
|
|
||||||
|
|
||||||
function Linker:include(includes)
|
|
||||||
if includes:handle(self:name()) then
|
|
||||||
includedirs {
|
|
||||||
path.join(ProjectFolder(), "Linker")
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Linker:link(links)
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function Linker:use()
|
|
||||||
dependson(self:name())
|
|
||||||
end
|
|
||||||
|
|
||||||
function Linker:name()
|
|
||||||
return "Linker"
|
|
||||||
end
|
|
||||||
|
|
||||||
function Linker:project()
|
|
||||||
local folder = ProjectFolder()
|
|
||||||
local includes = Includes:create()
|
|
||||||
local links = Links:create()
|
|
||||||
|
|
||||||
project(self:name())
|
|
||||||
targetdir(TargetDirectoryBin)
|
|
||||||
location "%{wks.location}/src/%{prj.name}"
|
|
||||||
kind "ConsoleApp"
|
|
||||||
language "C++"
|
|
||||||
|
|
||||||
files {
|
|
||||||
path.join(folder, "Linker/**.h"),
|
|
||||||
path.join(folder, "Linker/**.cpp")
|
|
||||||
}
|
|
||||||
|
|
||||||
self:include(includes)
|
|
||||||
Utils:include(includes)
|
|
||||||
ZoneLoading:include(includes)
|
|
||||||
ObjCompiling:include(includes)
|
|
||||||
ObjLoading:include(includes)
|
|
||||||
ObjWriting:include(includes)
|
|
||||||
ZoneWriting:include(includes)
|
|
||||||
|
|
||||||
Raw:use()
|
|
||||||
|
|
||||||
links:linkto(Utils)
|
|
||||||
links:linkto(ObjCompiling)
|
|
||||||
links:linkto(ZoneLoading)
|
|
||||||
links:linkto(ZoneWriting)
|
|
||||||
links:linkto(ObjLoading)
|
|
||||||
links:linkto(ObjWriting)
|
|
||||||
links:linkall()
|
|
||||||
end
|
|
||||||
@@ -1,480 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
class LinkerImpl final : public Linker
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
bool Start(const int argc, const char** argv) override
|
|
||||||
{
|
|
||||||
con::init();
|
|
||||||
|
|
||||||
auto shouldContinue = true;
|
|
||||||
if (!m_args.ParseArgs(argc, argv, shouldContinue))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!shouldContinue)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
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:
|
|
||||||
LinkerArgs m_args;
|
|
||||||
std::vector<std::unique_ptr<Zone>> m_loaded_zones;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<Linker> Linker::Create()
|
|
||||||
{
|
|
||||||
return std::make_unique<LinkerImpl>();
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#include "Linker.h"
|
|
||||||
|
|
||||||
int main(const int argc, const char** argv)
|
|
||||||
{
|
|
||||||
const auto linker = Linker::Create();
|
|
||||||
|
|
||||||
return linker->Start(argc, argv) ? 0 : 1;
|
|
||||||
}
|
|
||||||
49
src/LinkerCli.lua
Normal file
49
src/LinkerCli.lua
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
LinkerCli = {}
|
||||||
|
|
||||||
|
function LinkerCli:include(includes)
|
||||||
|
if includes:handle(self:name()) then
|
||||||
|
includedirs {
|
||||||
|
path.join(ProjectFolder(), "LinkerCli")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function LinkerCli:link(links)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function LinkerCli:use()
|
||||||
|
dependson(self:name())
|
||||||
|
end
|
||||||
|
|
||||||
|
function LinkerCli:name()
|
||||||
|
return "LinkerCli"
|
||||||
|
end
|
||||||
|
|
||||||
|
function LinkerCli:project()
|
||||||
|
local folder = ProjectFolder()
|
||||||
|
local includes = Includes:create()
|
||||||
|
local links = Links:create()
|
||||||
|
|
||||||
|
project(self:name())
|
||||||
|
targetdir(TargetDirectoryBin)
|
||||||
|
targetname "Linker"
|
||||||
|
location "%{wks.location}/src/%{prj.name}"
|
||||||
|
kind "ConsoleApp"
|
||||||
|
language "C++"
|
||||||
|
|
||||||
|
files {
|
||||||
|
path.join(folder, "LinkerCli/**.h"),
|
||||||
|
path.join(folder, "LinkerCli/**.cpp")
|
||||||
|
}
|
||||||
|
|
||||||
|
self:include(includes)
|
||||||
|
Utils:include(includes)
|
||||||
|
Linking:include(includes)
|
||||||
|
|
||||||
|
Raw:use()
|
||||||
|
|
||||||
|
links:linkto(Utils)
|
||||||
|
links:linkto(Linking)
|
||||||
|
links:linkall()
|
||||||
|
end
|
||||||
19
src/LinkerCli/main.cpp
Normal file
19
src/LinkerCli/main.cpp
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#include "Linker.h"
|
||||||
|
#include "Utils/Logging/Log.h"
|
||||||
|
|
||||||
|
int main(const int argc, const char** argv)
|
||||||
|
{
|
||||||
|
con::init();
|
||||||
|
|
||||||
|
LinkerArgs args;
|
||||||
|
auto shouldContinue = true;
|
||||||
|
if (!args.ParseArgs(argc, argv, shouldContinue))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (!shouldContinue)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
const auto linker = Linker::Create(std::move(args));
|
||||||
|
|
||||||
|
return linker->Start() ? 0 : 1;
|
||||||
|
}
|
||||||
61
src/Linking.lua
Normal file
61
src/Linking.lua
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
Linking = {}
|
||||||
|
|
||||||
|
function Linking:include(includes)
|
||||||
|
if includes:handle(self:name()) then
|
||||||
|
ZoneCommon:include(includes)
|
||||||
|
includedirs {
|
||||||
|
path.join(ProjectFolder(), "Linking")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Linking:link(links)
|
||||||
|
links:add(self:name())
|
||||||
|
links:linkto(Utils)
|
||||||
|
links:linkto(ObjCompiling)
|
||||||
|
links:linkto(ZoneLoading)
|
||||||
|
links:linkto(ZoneWriting)
|
||||||
|
links:linkto(ObjLoading)
|
||||||
|
links:linkto(ObjWriting)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Linking:use()
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function Linking:name()
|
||||||
|
return "Linking"
|
||||||
|
end
|
||||||
|
|
||||||
|
function Linking:project()
|
||||||
|
local folder = ProjectFolder()
|
||||||
|
local includes = Includes:create()
|
||||||
|
|
||||||
|
project(self:name())
|
||||||
|
targetdir(TargetDirectoryLib)
|
||||||
|
location "%{wks.location}/src/%{prj.name}"
|
||||||
|
kind "StaticLib"
|
||||||
|
language "C++"
|
||||||
|
|
||||||
|
files {
|
||||||
|
path.join(folder, "Linking/**.h"),
|
||||||
|
path.join(folder, "Linking/**.cpp")
|
||||||
|
}
|
||||||
|
|
||||||
|
vpaths {
|
||||||
|
["*"] = {
|
||||||
|
path.join(folder, "Linking")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjCommon:use()
|
||||||
|
useSourceTemplating("Linking")
|
||||||
|
|
||||||
|
self:include(includes)
|
||||||
|
Utils:include(includes)
|
||||||
|
ZoneLoading:include(includes)
|
||||||
|
ObjCompiling:include(includes)
|
||||||
|
ObjLoading:include(includes)
|
||||||
|
ObjWriting:include(includes)
|
||||||
|
ZoneWriting:include(includes)
|
||||||
|
end
|
||||||
476
src/Linking/Linker.cpp
Normal file
476
src/Linking/Linker.cpp
Normal 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));
|
||||||
|
}
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "LinkerArgs.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class Linker
|
class Linker
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Linker() = default;
|
|
||||||
virtual ~Linker() = default;
|
virtual ~Linker() = default;
|
||||||
|
|
||||||
Linker(const Linker& other) = delete;
|
static std::unique_ptr<Linker> Create(LinkerArgs args);
|
||||||
Linker(Linker&& other) noexcept = delete;
|
|
||||||
Linker& operator=(const Linker& other) = delete;
|
|
||||||
Linker& operator=(Linker&& other) noexcept = delete;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Starts the Linker application logic.
|
* \brief Starts the Linker application logic.
|
||||||
@@ -18,7 +17,5 @@ public:
|
|||||||
* \param argv The command line arguments.
|
* \param argv The command line arguments.
|
||||||
* \return \c true if the application was successful or \c false if an error occurred.
|
* \return \c true if the application was successful or \c false if an error occurred.
|
||||||
*/
|
*/
|
||||||
virtual bool Start(int argc, const char** argv) = 0;
|
virtual bool Start() = 0;
|
||||||
|
|
||||||
static std::unique_ptr<Linker> Create();
|
|
||||||
};
|
};
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
Unlinker = {}
|
|
||||||
|
|
||||||
function Unlinker:include(includes)
|
|
||||||
if includes:handle(self:name()) then
|
|
||||||
includedirs {
|
|
||||||
path.join(ProjectFolder(), "Unlinker")
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Unlinker:link(links)
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function Unlinker:use()
|
|
||||||
dependson(self:name())
|
|
||||||
end
|
|
||||||
|
|
||||||
function Unlinker:name()
|
|
||||||
return "Unlinker"
|
|
||||||
end
|
|
||||||
|
|
||||||
function Unlinker:project()
|
|
||||||
local folder = ProjectFolder()
|
|
||||||
local includes = Includes:create()
|
|
||||||
local links = Links:create()
|
|
||||||
|
|
||||||
project(self:name())
|
|
||||||
targetdir(TargetDirectoryBin)
|
|
||||||
location "%{wks.location}/src/%{prj.name}"
|
|
||||||
kind "ConsoleApp"
|
|
||||||
language "C++"
|
|
||||||
|
|
||||||
files {
|
|
||||||
path.join(folder, "Unlinker/**.h"),
|
|
||||||
path.join(folder, "Unlinker/**.cpp")
|
|
||||||
}
|
|
||||||
|
|
||||||
self:include(includes)
|
|
||||||
Utils:include(includes)
|
|
||||||
ZoneLoading:include(includes)
|
|
||||||
ObjLoading:include(includes)
|
|
||||||
ObjWriting:include(includes)
|
|
||||||
|
|
||||||
Raw:use()
|
|
||||||
|
|
||||||
links:linkto(Utils)
|
|
||||||
links:linkto(ZoneLoading)
|
|
||||||
links:linkto(ObjLoading)
|
|
||||||
links:linkto(ObjWriting)
|
|
||||||
links:linkall()
|
|
||||||
end
|
|
||||||
@@ -1,342 +0,0 @@
|
|||||||
#include "Unlinker.h"
|
|
||||||
|
|
||||||
#include "ContentLister/ContentPrinter.h"
|
|
||||||
#include "IObjLoader.h"
|
|
||||||
#include "IObjWriter.h"
|
|
||||||
#include "ObjWriting.h"
|
|
||||||
#include "SearchPath/IWD.h"
|
|
||||||
#include "SearchPath/OutputPathFilesystem.h"
|
|
||||||
#include "UnlinkerArgs.h"
|
|
||||||
#include "UnlinkerPaths.h"
|
|
||||||
#include "Utils/ClassUtils.h"
|
|
||||||
#include "Utils/Logging/Log.h"
|
|
||||||
#include "Utils/ObjFileStream.h"
|
|
||||||
#include "Zone/Definition/ZoneDefWriter.h"
|
|
||||||
#include "ZoneLoading.h"
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <format>
|
|
||||||
#include <fstream>
|
|
||||||
#include <regex>
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
class Unlinker::Impl
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* \copydoc Unlinker::Start
|
|
||||||
*/
|
|
||||||
bool Start(const int argc, const char** argv)
|
|
||||||
{
|
|
||||||
con::init();
|
|
||||||
|
|
||||||
auto shouldContinue = true;
|
|
||||||
if (!m_args.ParseArgs(argc, argv, shouldContinue))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!shouldContinue)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
UnlinkerPaths paths;
|
|
||||||
if (!paths.LoadUserPaths(m_args))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!LoadZones(paths))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const auto result = UnlinkZones(paths);
|
|
||||||
|
|
||||||
UnloadZones();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
[[nodiscard]] bool ShouldLoadObj() const
|
|
||||||
{
|
|
||||||
return m_args.m_task != UnlinkerArgs::ProcessingTask::LIST && !m_args.m_skip_obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool WriteZoneDefinitionFile(const Zone& zone, const fs::path& zoneDefinitionFileFolder) const
|
|
||||||
{
|
|
||||||
auto zoneDefinitionFilePath(zoneDefinitionFileFolder);
|
|
||||||
zoneDefinitionFilePath.append(zone.m_name);
|
|
||||||
zoneDefinitionFilePath.replace_extension(".zone");
|
|
||||||
|
|
||||||
std::ofstream zoneDefinitionFile(zoneDefinitionFilePath, std::fstream::out | std::fstream::binary);
|
|
||||||
if (!zoneDefinitionFile.is_open())
|
|
||||||
{
|
|
||||||
con::error("Failed to open file for zone definition file of zone \"{}\".", zone.m_name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto* zoneDefWriter = IZoneDefWriter::GetZoneDefWriterForGame(zone.m_game_id);
|
|
||||||
zoneDefWriter->WriteZoneDef(zoneDefinitionFile, zone, m_args.m_use_gdt);
|
|
||||||
|
|
||||||
zoneDefinitionFile.close();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool OpenGdtFile(const Zone& zone, const fs::path& outputFolder, std::ofstream& stream)
|
|
||||||
{
|
|
||||||
auto gdtFilePath(outputFolder);
|
|
||||||
gdtFilePath.append("source_data");
|
|
||||||
|
|
||||||
fs::create_directories(gdtFilePath);
|
|
||||||
|
|
||||||
gdtFilePath.append(zone.m_name);
|
|
||||||
gdtFilePath.replace_extension(".gdt");
|
|
||||||
|
|
||||||
stream = std::ofstream(gdtFilePath, std::fstream::out | std::fstream::binary);
|
|
||||||
if (!stream.is_open())
|
|
||||||
{
|
|
||||||
con::error("Failed to open file for zone definition file of zone \"{}\".", zone.m_name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateAssetIncludesAndExcludes(const AssetDumpingContext& context) const
|
|
||||||
{
|
|
||||||
const auto assetTypeCount = context.m_zone.m_pools->GetAssetTypeCount();
|
|
||||||
|
|
||||||
ObjWriting::Configuration.AssetTypesToHandleBitfield = std::vector<bool>(assetTypeCount);
|
|
||||||
|
|
||||||
std::vector<bool> handledSpecifiedAssets(m_args.m_specified_asset_types.size());
|
|
||||||
for (auto i = 0u; i < assetTypeCount; i++)
|
|
||||||
{
|
|
||||||
const auto assetTypeName = std::string(*context.m_zone.m_pools->GetAssetTypeName(i));
|
|
||||||
|
|
||||||
const auto foundSpecifiedEntry = m_args.m_specified_asset_type_map.find(assetTypeName);
|
|
||||||
if (foundSpecifiedEntry != m_args.m_specified_asset_type_map.end())
|
|
||||||
{
|
|
||||||
ObjWriting::Configuration.AssetTypesToHandleBitfield[i] = m_args.m_asset_type_handling == UnlinkerArgs::AssetTypeHandling::INCLUDE;
|
|
||||||
assert(foundSpecifiedEntry->second < handledSpecifiedAssets.size());
|
|
||||||
handledSpecifiedAssets[foundSpecifiedEntry->second] = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ObjWriting::Configuration.AssetTypesToHandleBitfield[i] = m_args.m_asset_type_handling == UnlinkerArgs::AssetTypeHandling::EXCLUDE;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto anySpecifiedValueInvalid = false;
|
|
||||||
for (auto i = 0u; i < handledSpecifiedAssets.size(); i++)
|
|
||||||
{
|
|
||||||
if (!handledSpecifiedAssets[i])
|
|
||||||
{
|
|
||||||
con::error("Unknown asset type \"{}\"", m_args.m_specified_asset_types[i]);
|
|
||||||
anySpecifiedValueInvalid = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (anySpecifiedValueInvalid)
|
|
||||||
{
|
|
||||||
con::error("Valid asset types are:");
|
|
||||||
|
|
||||||
auto first = true;
|
|
||||||
std::ostringstream ss;
|
|
||||||
for (auto i = 0u; i < assetTypeCount; i++)
|
|
||||||
{
|
|
||||||
const auto assetTypeName = std::string(*context.m_zone.m_pools->GetAssetTypeName(i));
|
|
||||||
|
|
||||||
if (first)
|
|
||||||
first = false;
|
|
||||||
else
|
|
||||||
ss << ", ";
|
|
||||||
ss << assetTypeName;
|
|
||||||
}
|
|
||||||
con::error(ss.str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \brief Performs the tasks specified by the command line arguments on the specified zone.
|
|
||||||
* \param searchPath The search path for obj data.
|
|
||||||
* \param zone The zone to handle.
|
|
||||||
* \return \c true if handling the zone was successful, otherwise \c false
|
|
||||||
*/
|
|
||||||
bool HandleZone(ISearchPath& searchPath, Zone& zone) const
|
|
||||||
{
|
|
||||||
if (m_args.m_task == UnlinkerArgs::ProcessingTask::LIST)
|
|
||||||
{
|
|
||||||
const ContentPrinter printer(zone);
|
|
||||||
printer.PrintContent();
|
|
||||||
}
|
|
||||||
else if (m_args.m_task == UnlinkerArgs::ProcessingTask::DUMP)
|
|
||||||
{
|
|
||||||
const auto outputFolderPathStr = m_args.GetOutputFolderPathForZone(zone);
|
|
||||||
const fs::path outputFolderPath(outputFolderPathStr);
|
|
||||||
fs::create_directories(outputFolderPath);
|
|
||||||
|
|
||||||
fs::path zoneDefinitionFileFolder(outputFolderPath);
|
|
||||||
zoneDefinitionFileFolder.append("zone_source");
|
|
||||||
fs::create_directories(zoneDefinitionFileFolder);
|
|
||||||
|
|
||||||
if (!WriteZoneDefinitionFile(zone, zoneDefinitionFileFolder))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
OutputPathFilesystem outputFolderOutputPath(outputFolderPath);
|
|
||||||
AssetDumpingContext context(zone, outputFolderPathStr, outputFolderOutputPath, searchPath, std::nullopt);
|
|
||||||
|
|
||||||
std::ofstream gdtStream;
|
|
||||||
if (m_args.m_use_gdt)
|
|
||||||
{
|
|
||||||
if (!OpenGdtFile(zone, outputFolderPath, gdtStream))
|
|
||||||
return false;
|
|
||||||
auto gdt = std::make_unique<GdtOutputStream>(gdtStream);
|
|
||||||
gdt->BeginStream();
|
|
||||||
|
|
||||||
const auto* game = IGame::GetGameById(zone.m_game_id);
|
|
||||||
gdt->WriteVersion(GdtVersion(game->GetShortName(), 1));
|
|
||||||
|
|
||||||
context.m_gdt = std::move(gdt);
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateAssetIncludesAndExcludes(context);
|
|
||||||
|
|
||||||
const auto* objWriter = IObjWriter::GetObjWriterForGame(zone.m_game_id);
|
|
||||||
|
|
||||||
auto result = objWriter->DumpZone(context);
|
|
||||||
|
|
||||||
if (m_args.m_use_gdt)
|
|
||||||
{
|
|
||||||
context.m_gdt->EndStream();
|
|
||||||
gdtStream.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result)
|
|
||||||
{
|
|
||||||
con::error("Dumping zone failed!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LoadZones(UnlinkerPaths& paths)
|
|
||||||
{
|
|
||||||
for (const auto& zonePath : m_args.m_zones_to_load)
|
|
||||||
{
|
|
||||||
if (!fs::is_regular_file(zonePath))
|
|
||||||
{
|
|
||||||
con::error("Could not find file \"{}\".", zonePath);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto absoluteZoneDirectory = absolute(std::filesystem::path(zonePath).remove_filename()).string();
|
|
||||||
|
|
||||||
auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory);
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (ShouldLoadObj())
|
|
||||||
{
|
|
||||||
const auto* objLoader = IObjLoader::GetObjLoaderForGame(zone->m_game_id);
|
|
||||||
objLoader->LoadReferencedContainersForZone(*searchPathsForZone, *zone);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
// Copy zone name since we deallocate before logging
|
|
||||||
const auto zoneName = loadedZone->m_name;
|
|
||||||
|
|
||||||
if (ShouldLoadObj())
|
|
||||||
{
|
|
||||||
const auto* objLoader = IObjLoader::GetObjLoaderForGame(loadedZone->m_game_id);
|
|
||||||
objLoader->UnloadContainersOfZone(*loadedZone);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadedZone.reset();
|
|
||||||
|
|
||||||
con::debug("Unloaded zone \"{}\"", zoneName);
|
|
||||||
}
|
|
||||||
m_loaded_zones.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool UnlinkZones(UnlinkerPaths& paths) const
|
|
||||||
{
|
|
||||||
for (const auto& zonePath : m_args.m_zones_to_unlink)
|
|
||||||
{
|
|
||||||
if (!fs::is_regular_file(zonePath))
|
|
||||||
{
|
|
||||||
con::error("Could not find file \"{}\".", zonePath);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto zoneDirectory = fs::path(zonePath).remove_filename();
|
|
||||||
if (zoneDirectory.empty())
|
|
||||||
zoneDirectory = fs::current_path();
|
|
||||||
auto absoluteZoneDirectory = absolute(zoneDirectory).string();
|
|
||||||
|
|
||||||
auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
const auto* objLoader = IObjLoader::GetObjLoaderForGame(zone->m_game_id);
|
|
||||||
if (ShouldLoadObj())
|
|
||||||
objLoader->LoadReferencedContainersForZone(*searchPathsForZone, *zone);
|
|
||||||
|
|
||||||
if (!HandleZone(*searchPathsForZone, *zone))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (ShouldLoadObj())
|
|
||||||
objLoader->UnloadContainersOfZone(*zone);
|
|
||||||
|
|
||||||
// Copy zone name for using it after freeing the zone
|
|
||||||
std::string zoneName = zone->m_name;
|
|
||||||
zone.reset();
|
|
||||||
con::debug("Unloaded zone \"{}\"", zoneName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
UnlinkerArgs m_args;
|
|
||||||
std::vector<std::unique_ptr<Zone>> m_loaded_zones;
|
|
||||||
};
|
|
||||||
|
|
||||||
Unlinker::Unlinker()
|
|
||||||
{
|
|
||||||
m_impl = new Impl();
|
|
||||||
}
|
|
||||||
|
|
||||||
Unlinker::~Unlinker()
|
|
||||||
{
|
|
||||||
delete m_impl;
|
|
||||||
m_impl = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Unlinker::Start(const int argc, const char** argv) const
|
|
||||||
{
|
|
||||||
return m_impl->Start(argc, argv);
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
class Unlinker
|
|
||||||
{
|
|
||||||
class Impl;
|
|
||||||
Impl* m_impl;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Unlinker();
|
|
||||||
~Unlinker();
|
|
||||||
|
|
||||||
Unlinker(const Unlinker& other) = delete;
|
|
||||||
Unlinker(Unlinker&& other) noexcept = delete;
|
|
||||||
Unlinker& operator=(const Unlinker& other) = delete;
|
|
||||||
Unlinker& operator=(Unlinker&& other) noexcept = delete;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \brief Starts the Unlinker application logic.
|
|
||||||
* \param argc The amount of command line arguments specified.
|
|
||||||
* \param argv The command line arguments.
|
|
||||||
* \return \c true if the application was successful or \c false if an error occurred.
|
|
||||||
*/
|
|
||||||
bool Start(int argc, const char** argv) const;
|
|
||||||
};
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#include "Unlinker.h"
|
|
||||||
|
|
||||||
int main(const int argc, const char** argv)
|
|
||||||
{
|
|
||||||
Unlinker unlinker;
|
|
||||||
|
|
||||||
return unlinker.Start(argc, argv) ? 0 : 1;
|
|
||||||
}
|
|
||||||
49
src/UnlinkerCli.lua
Normal file
49
src/UnlinkerCli.lua
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
UnlinkerCli = {}
|
||||||
|
|
||||||
|
function UnlinkerCli:include(includes)
|
||||||
|
if includes:handle(self:name()) then
|
||||||
|
includedirs {
|
||||||
|
path.join(ProjectFolder(), "UnlinkerCli")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function UnlinkerCli:link(links)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function UnlinkerCli:use()
|
||||||
|
dependson(self:name())
|
||||||
|
end
|
||||||
|
|
||||||
|
function UnlinkerCli:name()
|
||||||
|
return "UnlinkerCli"
|
||||||
|
end
|
||||||
|
|
||||||
|
function UnlinkerCli:project()
|
||||||
|
local folder = ProjectFolder()
|
||||||
|
local includes = Includes:create()
|
||||||
|
local links = Links:create()
|
||||||
|
|
||||||
|
project(self:name())
|
||||||
|
targetdir(TargetDirectoryBin)
|
||||||
|
targetname "Unlinker"
|
||||||
|
location "%{wks.location}/src/%{prj.name}"
|
||||||
|
kind "ConsoleApp"
|
||||||
|
language "C++"
|
||||||
|
|
||||||
|
files {
|
||||||
|
path.join(folder, "UnlinkerCli/**.h"),
|
||||||
|
path.join(folder, "UnlinkerCli/**.cpp")
|
||||||
|
}
|
||||||
|
|
||||||
|
self:include(includes)
|
||||||
|
Utils:include(includes)
|
||||||
|
Unlinking:include(includes)
|
||||||
|
|
||||||
|
Raw:use()
|
||||||
|
|
||||||
|
links:linkto(Utils)
|
||||||
|
links:linkto(Unlinking)
|
||||||
|
links:linkall()
|
||||||
|
end
|
||||||
19
src/UnlinkerCli/main.cpp
Normal file
19
src/UnlinkerCli/main.cpp
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#include "Unlinker.h"
|
||||||
|
#include "Utils/Logging/Log.h"
|
||||||
|
|
||||||
|
int main(const int argc, const char** argv)
|
||||||
|
{
|
||||||
|
con::init();
|
||||||
|
|
||||||
|
UnlinkerArgs args;
|
||||||
|
auto shouldContinue = true;
|
||||||
|
if (!args.ParseArgs(argc, argv, shouldContinue))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (!shouldContinue)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
const auto unlinker = Unlinker::Create(std::move(args));
|
||||||
|
|
||||||
|
return unlinker->Start() ? 0 : 1;
|
||||||
|
}
|
||||||
57
src/Unlinking.lua
Normal file
57
src/Unlinking.lua
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
Unlinking = {}
|
||||||
|
|
||||||
|
function Unlinking:include(includes)
|
||||||
|
if includes:handle(self:name()) then
|
||||||
|
ZoneCommon:include(includes)
|
||||||
|
includedirs {
|
||||||
|
path.join(ProjectFolder(), "Unlinking"),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Unlinking:link(links)
|
||||||
|
links:add(self:name())
|
||||||
|
links:linkto(Utils)
|
||||||
|
links:linkto(ZoneLoading)
|
||||||
|
links:linkto(ObjLoading)
|
||||||
|
links:linkto(ObjWriting)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Unlinking:use()
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function Unlinking:name()
|
||||||
|
return "Unlinking"
|
||||||
|
end
|
||||||
|
|
||||||
|
function Unlinking:project()
|
||||||
|
local folder = ProjectFolder()
|
||||||
|
local includes = Includes:create()
|
||||||
|
|
||||||
|
project(self:name())
|
||||||
|
targetdir(TargetDirectoryLib)
|
||||||
|
location "%{wks.location}/src/%{prj.name}"
|
||||||
|
kind "StaticLib"
|
||||||
|
language "C++"
|
||||||
|
|
||||||
|
files {
|
||||||
|
path.join(folder, "Unlinking/**.h"),
|
||||||
|
path.join(folder, "Unlinking/**.cpp")
|
||||||
|
}
|
||||||
|
|
||||||
|
vpaths {
|
||||||
|
["*"] = {
|
||||||
|
path.join(folder, "Unlinking")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjCommon:use()
|
||||||
|
useSourceTemplating("Unlinking")
|
||||||
|
|
||||||
|
self:include(includes)
|
||||||
|
Utils:include(includes)
|
||||||
|
ZoneLoading:include(includes)
|
||||||
|
ObjLoading:include(includes)
|
||||||
|
ObjWriting:include(includes)
|
||||||
|
end
|
||||||
327
src/Unlinking/Unlinker.cpp
Normal file
327
src/Unlinking/Unlinker.cpp
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
#include "Unlinker.h"
|
||||||
|
|
||||||
|
#include "ContentLister/ContentPrinter.h"
|
||||||
|
#include "IObjLoader.h"
|
||||||
|
#include "IObjWriter.h"
|
||||||
|
#include "ObjWriting.h"
|
||||||
|
#include "SearchPath/IWD.h"
|
||||||
|
#include "SearchPath/OutputPathFilesystem.h"
|
||||||
|
#include "UnlinkerArgs.h"
|
||||||
|
#include "UnlinkerPaths.h"
|
||||||
|
#include "Utils/ClassUtils.h"
|
||||||
|
#include "Utils/Logging/Log.h"
|
||||||
|
#include "Utils/ObjFileStream.h"
|
||||||
|
#include "Zone/Definition/ZoneDefWriter.h"
|
||||||
|
#include "ZoneLoading.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <format>
|
||||||
|
#include <fstream>
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
class UnlinkerImpl : public Unlinker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
UnlinkerImpl(UnlinkerArgs args)
|
||||||
|
: m_args(std::move(args))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Start() override
|
||||||
|
{
|
||||||
|
UnlinkerPaths paths;
|
||||||
|
if (!paths.LoadUserPaths(m_args))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!LoadZones(paths))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const auto result = UnlinkZones(paths);
|
||||||
|
|
||||||
|
UnloadZones();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] bool ShouldLoadObj() const
|
||||||
|
{
|
||||||
|
return m_args.m_task != UnlinkerArgs::ProcessingTask::LIST && !m_args.m_skip_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool WriteZoneDefinitionFile(const Zone& zone, const fs::path& zoneDefinitionFileFolder) const
|
||||||
|
{
|
||||||
|
auto zoneDefinitionFilePath(zoneDefinitionFileFolder);
|
||||||
|
zoneDefinitionFilePath.append(zone.m_name);
|
||||||
|
zoneDefinitionFilePath.replace_extension(".zone");
|
||||||
|
|
||||||
|
std::ofstream zoneDefinitionFile(zoneDefinitionFilePath, std::fstream::out | std::fstream::binary);
|
||||||
|
if (!zoneDefinitionFile.is_open())
|
||||||
|
{
|
||||||
|
con::error("Failed to open file for zone definition file of zone \"{}\".", zone.m_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* zoneDefWriter = IZoneDefWriter::GetZoneDefWriterForGame(zone.m_game_id);
|
||||||
|
zoneDefWriter->WriteZoneDef(zoneDefinitionFile, zone, m_args.m_use_gdt);
|
||||||
|
|
||||||
|
zoneDefinitionFile.close();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool OpenGdtFile(const Zone& zone, const fs::path& outputFolder, std::ofstream& stream)
|
||||||
|
{
|
||||||
|
auto gdtFilePath(outputFolder);
|
||||||
|
gdtFilePath.append("source_data");
|
||||||
|
|
||||||
|
fs::create_directories(gdtFilePath);
|
||||||
|
|
||||||
|
gdtFilePath.append(zone.m_name);
|
||||||
|
gdtFilePath.replace_extension(".gdt");
|
||||||
|
|
||||||
|
stream = std::ofstream(gdtFilePath, std::fstream::out | std::fstream::binary);
|
||||||
|
if (!stream.is_open())
|
||||||
|
{
|
||||||
|
con::error("Failed to open file for zone definition file of zone \"{}\".", zone.m_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateAssetIncludesAndExcludes(const AssetDumpingContext& context) const
|
||||||
|
{
|
||||||
|
const auto assetTypeCount = context.m_zone.m_pools->GetAssetTypeCount();
|
||||||
|
|
||||||
|
ObjWriting::Configuration.AssetTypesToHandleBitfield = std::vector<bool>(assetTypeCount);
|
||||||
|
|
||||||
|
std::vector<bool> handledSpecifiedAssets(m_args.m_specified_asset_types.size());
|
||||||
|
for (auto i = 0u; i < assetTypeCount; i++)
|
||||||
|
{
|
||||||
|
const auto assetTypeName = std::string(*context.m_zone.m_pools->GetAssetTypeName(i));
|
||||||
|
|
||||||
|
const auto foundSpecifiedEntry = m_args.m_specified_asset_type_map.find(assetTypeName);
|
||||||
|
if (foundSpecifiedEntry != m_args.m_specified_asset_type_map.end())
|
||||||
|
{
|
||||||
|
ObjWriting::Configuration.AssetTypesToHandleBitfield[i] = m_args.m_asset_type_handling == UnlinkerArgs::AssetTypeHandling::INCLUDE;
|
||||||
|
assert(foundSpecifiedEntry->second < handledSpecifiedAssets.size());
|
||||||
|
handledSpecifiedAssets[foundSpecifiedEntry->second] = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ObjWriting::Configuration.AssetTypesToHandleBitfield[i] = m_args.m_asset_type_handling == UnlinkerArgs::AssetTypeHandling::EXCLUDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto anySpecifiedValueInvalid = false;
|
||||||
|
for (auto i = 0u; i < handledSpecifiedAssets.size(); i++)
|
||||||
|
{
|
||||||
|
if (!handledSpecifiedAssets[i])
|
||||||
|
{
|
||||||
|
con::error("Unknown asset type \"{}\"", m_args.m_specified_asset_types[i]);
|
||||||
|
anySpecifiedValueInvalid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anySpecifiedValueInvalid)
|
||||||
|
{
|
||||||
|
con::error("Valid asset types are:");
|
||||||
|
|
||||||
|
auto first = true;
|
||||||
|
std::ostringstream ss;
|
||||||
|
for (auto i = 0u; i < assetTypeCount; i++)
|
||||||
|
{
|
||||||
|
const auto assetTypeName = std::string(*context.m_zone.m_pools->GetAssetTypeName(i));
|
||||||
|
|
||||||
|
if (first)
|
||||||
|
first = false;
|
||||||
|
else
|
||||||
|
ss << ", ";
|
||||||
|
ss << assetTypeName;
|
||||||
|
}
|
||||||
|
con::error(ss.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Performs the tasks specified by the command line arguments on the specified zone.
|
||||||
|
* \param searchPath The search path for obj data.
|
||||||
|
* \param zone The zone to handle.
|
||||||
|
* \return \c true if handling the zone was successful, otherwise \c false
|
||||||
|
*/
|
||||||
|
bool HandleZone(ISearchPath& searchPath, Zone& zone) const
|
||||||
|
{
|
||||||
|
if (m_args.m_task == UnlinkerArgs::ProcessingTask::LIST)
|
||||||
|
{
|
||||||
|
const ContentPrinter printer(zone);
|
||||||
|
printer.PrintContent();
|
||||||
|
}
|
||||||
|
else if (m_args.m_task == UnlinkerArgs::ProcessingTask::DUMP)
|
||||||
|
{
|
||||||
|
const auto outputFolderPathStr = m_args.GetOutputFolderPathForZone(zone);
|
||||||
|
const fs::path outputFolderPath(outputFolderPathStr);
|
||||||
|
fs::create_directories(outputFolderPath);
|
||||||
|
|
||||||
|
fs::path zoneDefinitionFileFolder(outputFolderPath);
|
||||||
|
zoneDefinitionFileFolder.append("zone_source");
|
||||||
|
fs::create_directories(zoneDefinitionFileFolder);
|
||||||
|
|
||||||
|
if (!WriteZoneDefinitionFile(zone, zoneDefinitionFileFolder))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
OutputPathFilesystem outputFolderOutputPath(outputFolderPath);
|
||||||
|
AssetDumpingContext context(zone, outputFolderPathStr, outputFolderOutputPath, searchPath, std::nullopt);
|
||||||
|
|
||||||
|
std::ofstream gdtStream;
|
||||||
|
if (m_args.m_use_gdt)
|
||||||
|
{
|
||||||
|
if (!OpenGdtFile(zone, outputFolderPath, gdtStream))
|
||||||
|
return false;
|
||||||
|
auto gdt = std::make_unique<GdtOutputStream>(gdtStream);
|
||||||
|
gdt->BeginStream();
|
||||||
|
|
||||||
|
const auto* game = IGame::GetGameById(zone.m_game_id);
|
||||||
|
gdt->WriteVersion(GdtVersion(game->GetShortName(), 1));
|
||||||
|
|
||||||
|
context.m_gdt = std::move(gdt);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateAssetIncludesAndExcludes(context);
|
||||||
|
|
||||||
|
const auto* objWriter = IObjWriter::GetObjWriterForGame(zone.m_game_id);
|
||||||
|
|
||||||
|
auto result = objWriter->DumpZone(context);
|
||||||
|
|
||||||
|
if (m_args.m_use_gdt)
|
||||||
|
{
|
||||||
|
context.m_gdt->EndStream();
|
||||||
|
gdtStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
con::error("Dumping zone failed!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoadZones(UnlinkerPaths& paths)
|
||||||
|
{
|
||||||
|
for (const auto& zonePath : m_args.m_zones_to_load)
|
||||||
|
{
|
||||||
|
if (!fs::is_regular_file(zonePath))
|
||||||
|
{
|
||||||
|
con::error("Could not find file \"{}\".", zonePath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto absoluteZoneDirectory = absolute(std::filesystem::path(zonePath).remove_filename()).string();
|
||||||
|
|
||||||
|
auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory);
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (ShouldLoadObj())
|
||||||
|
{
|
||||||
|
const auto* objLoader = IObjLoader::GetObjLoaderForGame(zone->m_game_id);
|
||||||
|
objLoader->LoadReferencedContainersForZone(*searchPathsForZone, *zone);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Copy zone name since we deallocate before logging
|
||||||
|
const auto zoneName = loadedZone->m_name;
|
||||||
|
|
||||||
|
if (ShouldLoadObj())
|
||||||
|
{
|
||||||
|
const auto* objLoader = IObjLoader::GetObjLoaderForGame(loadedZone->m_game_id);
|
||||||
|
objLoader->UnloadContainersOfZone(*loadedZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedZone.reset();
|
||||||
|
|
||||||
|
con::debug("Unloaded zone \"{}\"", zoneName);
|
||||||
|
}
|
||||||
|
m_loaded_zones.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnlinkZones(UnlinkerPaths& paths) const
|
||||||
|
{
|
||||||
|
for (const auto& zonePath : m_args.m_zones_to_unlink)
|
||||||
|
{
|
||||||
|
if (!fs::is_regular_file(zonePath))
|
||||||
|
{
|
||||||
|
con::error("Could not find file \"{}\".", zonePath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto zoneDirectory = fs::path(zonePath).remove_filename();
|
||||||
|
if (zoneDirectory.empty())
|
||||||
|
zoneDirectory = fs::current_path();
|
||||||
|
auto absoluteZoneDirectory = absolute(zoneDirectory).string();
|
||||||
|
|
||||||
|
auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
const auto* objLoader = IObjLoader::GetObjLoaderForGame(zone->m_game_id);
|
||||||
|
if (ShouldLoadObj())
|
||||||
|
objLoader->LoadReferencedContainersForZone(*searchPathsForZone, *zone);
|
||||||
|
|
||||||
|
if (!HandleZone(*searchPathsForZone, *zone))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (ShouldLoadObj())
|
||||||
|
objLoader->UnloadContainersOfZone(*zone);
|
||||||
|
|
||||||
|
// Copy zone name for using it after freeing the zone
|
||||||
|
std::string zoneName = zone->m_name;
|
||||||
|
zone.reset();
|
||||||
|
con::debug("Unloaded zone \"{}\"", zoneName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnlinkerArgs m_args;
|
||||||
|
std::vector<std::unique_ptr<Zone>> m_loaded_zones;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::unique_ptr<Unlinker> Unlinker::Create(UnlinkerArgs args)
|
||||||
|
{
|
||||||
|
return std::make_unique<UnlinkerImpl>(std::move(args));
|
||||||
|
}
|
||||||
19
src/Unlinking/Unlinker.h
Normal file
19
src/Unlinking/Unlinker.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "UnlinkerArgs.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class Unlinker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~Unlinker() = default;
|
||||||
|
|
||||||
|
static std::unique_ptr<Unlinker> Create(UnlinkerArgs args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Starts the Unlinker application logic.
|
||||||
|
* \return \c true if the application was successful or \c false if an error occurred.
|
||||||
|
*/
|
||||||
|
virtual bool Start() = 0;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user