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

Merge pull request #619 from Laupetin/fix/asset-references-in-global-assets

fix: asset references in global assets
This commit is contained in:
Jan
2025-12-25 16:11:52 +01:00
committed by GitHub
101 changed files with 3095 additions and 1207 deletions

View File

@@ -78,6 +78,7 @@ jobs:
./ObjLoadingTests ./ObjLoadingTests
./ObjWritingTests ./ObjWritingTests
./ParserTests ./ParserTests
./SystemTests
./ZoneCodeGeneratorLibTests ./ZoneCodeGeneratorLibTests
./ZoneCommonTests ./ZoneCommonTests
@@ -138,6 +139,8 @@ jobs:
$combinedExitCode = [System.Math]::max($combinedExitCode, $LASTEXITCODE) $combinedExitCode = [System.Math]::max($combinedExitCode, $LASTEXITCODE)
./ParserTests ./ParserTests
$combinedExitCode = [System.Math]::max($combinedExitCode, $LASTEXITCODE) $combinedExitCode = [System.Math]::max($combinedExitCode, $LASTEXITCODE)
./SystemTests
$combinedExitCode = [System.Math]::max($combinedExitCode, $LASTEXITCODE)
./ZoneCodeGeneratorLibTests ./ZoneCodeGeneratorLibTests
$combinedExitCode = [System.Math]::max($combinedExitCode, $LASTEXITCODE) $combinedExitCode = [System.Math]::max($combinedExitCode, $LASTEXITCODE)
./ZoneCommonTests ./ZoneCommonTests

View File

@@ -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
@@ -199,6 +203,7 @@ include "test/ObjLoadingTests.lua"
include "test/ObjWritingTests.lua" include "test/ObjWritingTests.lua"
include "test/ParserTestUtils.lua" include "test/ParserTestUtils.lua"
include "test/ParserTests.lua" include "test/ParserTests.lua"
include "test/SystemTests.lua"
include "test/ZoneCodeGeneratorLibTests.lua" include "test/ZoneCodeGeneratorLibTests.lua"
include "test/ZoneCommonTests.lua" include "test/ZoneCommonTests.lua"
@@ -212,6 +217,7 @@ group "Tests"
ObjWritingTests:project() ObjWritingTests:project()
ParserTestUtils:project() ParserTestUtils:project()
ParserTests:project() ParserTests:project()
SystemTests:project()
ZoneCodeGeneratorLibTests:project() ZoneCodeGeneratorLibTests:project()
ZoneCommonTests:project() ZoneCommonTests:project()
group "" group ""

View File

@@ -18,11 +18,11 @@ public:
template<typename AssetType> struct AssetNameAccessor template<typename AssetType> struct AssetNameAccessor
{ {
public:
static_assert(std::is_base_of_v<IAssetBase, AssetType>); static_assert(std::is_base_of_v<IAssetBase, AssetType>);
// static constexpr bool IS_SINGLETON = false; // static constexpr bool IS_SINGLETON = false;
// using RETURN_TYPE = const char*&;
// const char*& operator()(AssetType::Type& asset) // static RETURN_TYPE GetAssetName(assetType::Type& asset)
// { // {
// throw std::runtime_error("Not implemented"); // throw std::runtime_error("Not implemented");
// } // }
@@ -34,8 +34,9 @@ public:
public: \ public: \
static_assert(std::is_base_of_v<IAssetBase, assetType>); \ static_assert(std::is_base_of_v<IAssetBase, assetType>); \
static constexpr bool IS_SINGLETON = false; \ static constexpr bool IS_SINGLETON = false; \
using RETURN_TYPE = const char*&; \
\ \
const char*& operator()(assetType::Type& asset) \ static RETURN_TYPE GetAssetName(assetType::Type& asset) \
{ \ { \
return asset.nameProperty; \ return asset.nameProperty; \
} \ } \
@@ -47,10 +48,17 @@ public:
public: \ public: \
static_assert(std::is_base_of_v<IAssetBase, assetType>); \ static_assert(std::is_base_of_v<IAssetBase, assetType>); \
static constexpr bool IS_SINGLETON = true; \ static constexpr bool IS_SINGLETON = true; \
using RETURN_TYPE = const char* const&; \
\ \
const char* const& operator()(assetType::Type& asset) \ static RETURN_TYPE GetAssetName(assetType::Type& asset) \
{ \ { \
static const char* NAME = singletonName; \ static const char* NAME = singletonName; \
return NAME; \ return NAME; \
} \ } \
} }
template<typename AssetType> AssetNameAccessor<AssetType>::RETURN_TYPE AssetName(typename AssetType::Type& asset)
{
static_assert(std::is_base_of_v<IAssetBase, AssetType>);
return AssetNameAccessor<AssetType>::GetAssetName(asset);
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -104,7 +104,7 @@ XAssetInfoGeneric* AssetCreationContext::LoadDefaultAssetDependency(const asset_
return nullptr; return nullptr;
} }
XAssetInfoGeneric* AssetCreationContext::LoadDependencyGeneric(const asset_type_t assetType, const std::string& assetName) XAssetInfoGeneric* AssetCreationContext::LoadDependencyGeneric(const asset_type_t assetType, const std::string& assetName, bool required)
{ {
auto* alreadyLoadedAsset = m_zone.m_pools->GetAssetOrAssetReference(assetType, assetName); auto* alreadyLoadedAsset = m_zone.m_pools->GetAssetOrAssetReference(assetType, assetName);
if (alreadyLoadedAsset) if (alreadyLoadedAsset)
@@ -131,7 +131,7 @@ XAssetInfoGeneric* AssetCreationContext::LoadDependencyGeneric(const asset_type_
con::error("Could not load asset \"{}\" of type \"{}\"", assetName, *m_zone.m_pools->GetAssetTypeName(assetType)); con::error("Could not load asset \"{}\" of type \"{}\"", assetName, *m_zone.m_pools->GetAssetTypeName(assetType));
} }
else else if (required)
{ {
con::error("Missing asset \"{}\" of type \"{}\"", assetName, *m_zone.m_pools->GetAssetTypeName(assetType)); con::error("Missing asset \"{}\" of type \"{}\"", assetName, *m_zone.m_pools->GetAssetTypeName(assetType));
} }

View File

@@ -54,7 +54,7 @@ public:
return static_cast<XAssetInfo<typename AssetType::Type>*>(LoadDependencyGeneric(AssetType::EnumEntry, assetName)); return static_cast<XAssetInfo<typename AssetType::Type>*>(LoadDependencyGeneric(AssetType::EnumEntry, assetName));
} }
XAssetInfoGeneric* LoadDependencyGeneric(asset_type_t assetType, const std::string& assetName); XAssetInfoGeneric* LoadDependencyGeneric(asset_type_t assetType, const std::string& assetName, bool required = true);
template<typename AssetType> IndirectAssetReference LoadIndirectAssetReference(const std::string& assetName) template<typename AssetType> IndirectAssetReference LoadIndirectAssetReference(const std::string& assetName)
{ {

View File

@@ -0,0 +1,61 @@
#include "GlobalAssetPoolsLoader.h"
GlobalAssetPoolsRegistrationPreparation::GlobalAssetPoolsRegistrationPreparation(GenericAssetRegistration& registration,
Zone& zone,
Zone& foreignZone,
AssetCreationContext& context)
: m_registration(registration),
m_zone(zone),
m_foreign_zone(foreignZone),
m_context(context),
m_failure(false)
{
}
std::optional<XAssetInfoGeneric*> GlobalAssetPoolsRegistrationPreparation::Visit_Dependency(const asset_type_t assetType, const char* assetName)
{
if (assetName && assetName[0] == ',')
{
/*
Try to load the actual asset when the asset from the global asset pools just references one.
If that fails, keep the reference to not destroy previous existing behaviour of just being able to use assets from the global pools
without ignores.
*/
auto* nonReferenceAssetName = &assetName[1];
auto* assetDependency = m_context.LoadDependencyGeneric(assetType, nonReferenceAssetName, false);
if (assetDependency)
{
m_registration.AddDependency(assetDependency);
return std::nullopt;
}
}
auto* newDependency = m_context.LoadDependencyGeneric(assetType, assetName);
if (newDependency)
{
m_registration.AddDependency(newDependency);
return std::nullopt;
}
m_failure = true;
return std::nullopt;
}
std::optional<scr_string_t> GlobalAssetPoolsRegistrationPreparation::Visit_ScriptString(scr_string_t scriptString)
{
// Make sure any used script string is available in the created zone
m_zone.m_script_strings.AddOrGetScriptString(m_foreign_zone.m_script_strings.CValue(scriptString));
m_registration.AddScriptString(scriptString);
return std::nullopt;
}
void GlobalAssetPoolsRegistrationPreparation::Visit_IndirectAssetRef(const asset_type_t assetType, const char* assetName)
{
m_registration.AddIndirectAssetReference(m_context.LoadIndirectAssetReferenceGeneric(assetType, assetName));
}
bool GlobalAssetPoolsRegistrationPreparation::Failed() const
{
return m_failure;
}

View File

@@ -1,13 +1,36 @@
#pragma once #pragma once
#include "Asset/IAssetCreator.h" #include "Asset/IAssetCreator.h"
#include "Marking/AssetVisitor.h"
#include "Marking/BaseAssetMarker.h"
#include "Pool/GlobalAssetPool.h" #include "Pool/GlobalAssetPool.h"
class GlobalAssetPoolsRegistrationPreparation : public AssetVisitor
{
public:
GlobalAssetPoolsRegistrationPreparation(GenericAssetRegistration& registration, Zone& zone, Zone& foreignZone, AssetCreationContext& context);
std::optional<XAssetInfoGeneric*> Visit_Dependency(asset_type_t assetType, const char* assetName) override;
std::optional<scr_string_t> Visit_ScriptString(scr_string_t scriptString) override;
void Visit_IndirectAssetRef(asset_type_t assetType, const char* assetName) override;
[[nodiscard]] bool Failed() const;
private:
GenericAssetRegistration& m_registration;
Zone& m_zone;
Zone& m_foreign_zone;
AssetCreationContext& m_context;
bool m_failure;
};
template<typename AssetType> class GlobalAssetPoolsLoader : public AssetCreator<AssetType> template<typename AssetType> class GlobalAssetPoolsLoader : public AssetCreator<AssetType>
{ {
public: public:
static_assert(std::is_base_of_v<IAssetBase, AssetType>); static_assert(std::is_base_of_v<IAssetBase, AssetType>);
GlobalAssetPoolsLoader(Zone& zone) explicit GlobalAssetPoolsLoader(Zone& zone)
: m_zone(zone) : m_zone(zone)
{ {
} }
@@ -21,26 +44,12 @@ public:
AssetRegistration<AssetType> registration(assetName, existingAsset->Asset()); AssetRegistration<AssetType> registration(assetName, existingAsset->Asset());
for (const auto* dependency : existingAsset->m_dependencies) GlobalAssetPoolsRegistrationPreparation registrationPreparation(registration, m_zone, *existingAsset->m_zone, context);
{ AssetMarker<AssetType> marker(registrationPreparation);
auto* newDependency = context.LoadDependencyGeneric(dependency->m_type, dependency->m_name); marker.Mark(existingAsset->Asset());
if (newDependency)
registration.AddDependency(newDependency);
else
return AssetCreationResult::Failure();
}
for (const auto& indirectAssetReference : existingAsset->m_indirect_asset_references)
registration.AddIndirectAssetReference(context.LoadIndirectAssetReferenceGeneric(indirectAssetReference.m_type, indirectAssetReference.m_name));
// Make sure any used script string is available in the created zone
// The replacement of the scr_string_t values will be done upon writing
for (const auto scrString : existingAsset->m_used_script_strings)
m_zone.m_script_strings.AddOrGetScriptString(existingAsset->m_zone->m_script_strings.CValue(scrString));
auto* newAsset = context.AddAsset(std::move(registration)); auto* newAsset = context.AddAsset(std::move(registration));
// Make sure we remember this asset came from a different zone
// Make sure we remember this asset came from another zone
newAsset->m_zone = existingAsset->m_zone; newAsset->m_zone = existingAsset->m_zone;
return AssetCreationResult::Success(newAsset); return AssetCreationResult::Success(newAsset);

View File

@@ -42,7 +42,7 @@ public:
AssetCreationResult CreateDefaultAsset(const std::string& assetName, AssetCreationContext& context) const override AssetCreationResult CreateDefaultAsset(const std::string& assetName, AssetCreationContext& context) const override
{ {
auto* asset = m_memory.Alloc<typename AssetType::Type>(); auto* asset = m_memory.Alloc<typename AssetType::Type>();
AssetNameAccessor<AssetType>{}(*asset) = m_memory.Dup(assetName.c_str()); AssetName<AssetType>(*asset) = m_memory.Dup(assetName.c_str());
return AssetCreationResult::Success(context.AddAsset<AssetType>(assetName, asset)); return AssetCreationResult::Success(context.AddAsset<AssetType>(assetName, asset));
} }

View File

@@ -1,6 +1,7 @@
#include "ObjLoaderIW3.h" #include "ObjLoaderIW3.h"
#include "Asset/GlobalAssetPoolsLoader.h" #include "Asset/GlobalAssetPoolsLoader.h"
#include "Game/IW3/AssetMarkerIW3.h"
#include "Game/IW3/GameIW3.h" #include "Game/IW3/GameIW3.h"
#include "Game/IW3/IW3.h" #include "Game/IW3/IW3.h"
#include "Game/IW3/XModel/LoaderXModelIW3.h" #include "Game/IW3/XModel/LoaderXModelIW3.h"
@@ -65,7 +66,7 @@ namespace
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSound>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSound>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSoundCurve>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSoundCurve>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetLoadedSound>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetLoadedSound>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetClipMap>>(zone)); // collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetClipMap>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetClipMapPvs>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetClipMapPvs>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetComWorld>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetComWorld>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetGameWorldSp>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetGameWorldSp>>(zone));
@@ -78,7 +79,7 @@ namespace
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetMenu>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetMenu>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetLocalize>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetLocalize>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetWeapon>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetWeapon>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSoundDriverGlobals>>(zone)); // collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSoundDriverGlobals>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetFx>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetFx>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetImpactFx>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetImpactFx>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetRawFile>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetRawFile>>(zone));

View File

@@ -1,6 +1,7 @@
#include "ObjLoaderIW4.h" #include "ObjLoaderIW4.h"
#include "Asset/GlobalAssetPoolsLoader.h" #include "Asset/GlobalAssetPoolsLoader.h"
#include "Game/IW4/AssetMarkerIW4.h"
#include "Game/IW4/GameIW4.h" #include "Game/IW4/GameIW4.h"
#include "Game/IW4/IW4.h" #include "Game/IW4/IW4.h"
#include "Game/IW4/XModel/LoaderXModelIW4.h" #include "Game/IW4/XModel/LoaderXModelIW4.h"
@@ -79,7 +80,7 @@ namespace
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetPhysPreset>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetPhysPreset>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetPhysCollMap>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetPhysCollMap>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetXAnim>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetXAnim>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetXModelSurfs>>(zone)); // collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetXModelSurfs>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetXModel>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetXModel>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetMaterial>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetMaterial>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetPixelShader>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetPixelShader>>(zone));
@@ -90,7 +91,7 @@ namespace
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSound>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSound>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSoundCurve>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSoundCurve>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetLoadedSound>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetLoadedSound>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetClipMapSp>>(zone)); // collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetClipMapSp>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetClipMapMp>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetClipMapMp>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetComWorld>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetComWorld>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetGameWorldSp>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetGameWorldSp>>(zone));

View File

@@ -1,6 +1,7 @@
#include "ObjLoaderIW5.h" #include "ObjLoaderIW5.h"
#include "Asset/GlobalAssetPoolsLoader.h" #include "Asset/GlobalAssetPoolsLoader.h"
#include "Game/IW5/AssetMarkerIW5.h"
#include "Game/IW5/GameIW5.h" #include "Game/IW5/GameIW5.h"
#include "Game/IW5/IW5.h" #include "Game/IW5/IW5.h"
#include "Game/IW5/XModel/LoaderXModelIW5.h" #include "Game/IW5/XModel/LoaderXModelIW5.h"

View File

@@ -1,6 +1,7 @@
#include "ObjLoaderT5.h" #include "ObjLoaderT5.h"
#include "Asset/GlobalAssetPoolsLoader.h" #include "Asset/GlobalAssetPoolsLoader.h"
#include "Game/T5/AssetMarkerT5.h"
#include "Game/T5/GameT5.h" #include "Game/T5/GameT5.h"
#include "Game/T5/T5.h" #include "Game/T5/T5.h"
#include "Game/T5/XModel/LoaderXModelT5.h" #include "Game/T5/XModel/LoaderXModelT5.h"
@@ -71,7 +72,7 @@ namespace
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetImage>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetImage>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSoundBank>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSoundBank>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSoundPatch>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSoundPatch>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetClipMap>>(zone)); // collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetClipMap>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetClipMapPvs>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetClipMapPvs>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetComWorld>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetComWorld>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetGameWorldSp>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetGameWorldSp>>(zone));

View File

@@ -3,6 +3,7 @@
#include "Asset/GlobalAssetPoolsLoader.h" #include "Asset/GlobalAssetPoolsLoader.h"
#include "FontIcon/CsvLoaderFontIconT6.h" #include "FontIcon/CsvLoaderFontIconT6.h"
#include "FontIcon/JsonLoaderFontIconT6.h" #include "FontIcon/JsonLoaderFontIconT6.h"
#include "Game/T6/AssetMarkerT6.h"
#include "Game/T6/CommonT6.h" #include "Game/T6/CommonT6.h"
#include "Game/T6/GameAssetPoolT6.h" #include "Game/T6/GameAssetPoolT6.h"
#include "Game/T6/GameT6.h" #include "Game/T6/GameT6.h"
@@ -340,7 +341,7 @@ namespace T6
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetImage>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetImage>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSoundBank>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSoundBank>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSoundPatch>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetSoundPatch>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetClipMap>>(zone)); // collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetClipMap>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetClipMapPvs>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetClipMapPvs>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetComWorld>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetComWorld>>(zone));
collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetGameWorldSp>>(zone)); collection.AddAssetCreator(std::make_unique<GlobalAssetPoolsLoader<AssetGameWorldSp>>(zone));

View File

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

View File

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

View File

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

View File

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

View File

@@ -225,6 +225,20 @@ function ZoneCode:allTestFiles()
return result return result
end end
function ZoneCode:allMarkFiles()
result = {}
for game, assets in pairs(self.Assets) do
for i, assetName in ipairs(assets) do
local assetNameLower = string.lower(assetName)
table.insert(result, "%{wks.location}/src/ZoneCode/Game/" .. game .. "/XAssets/" .. assetNameLower .. "/" .. assetNameLower .. "_mark_db.cpp")
table.insert(result, "%{wks.location}/src/ZoneCode/Game/" .. game .. "/XAssets/" .. assetNameLower .. "/" .. assetNameLower .. "_mark_db.h")
end
end
return result
end
function ZoneCode:allLoadFiles() function ZoneCode:allLoadFiles()
result = {} result = {}
@@ -233,8 +247,6 @@ function ZoneCode:allLoadFiles()
local assetNameLower = string.lower(assetName) local assetNameLower = string.lower(assetName)
table.insert(result, "%{wks.location}/src/ZoneCode/Game/" .. game .. "/XAssets/" .. assetNameLower .. "/" .. assetNameLower .. "_load_db.cpp") table.insert(result, "%{wks.location}/src/ZoneCode/Game/" .. game .. "/XAssets/" .. assetNameLower .. "/" .. assetNameLower .. "_load_db.cpp")
table.insert(result, "%{wks.location}/src/ZoneCode/Game/" .. game .. "/XAssets/" .. assetNameLower .. "/" .. assetNameLower .. "_load_db.h") table.insert(result, "%{wks.location}/src/ZoneCode/Game/" .. game .. "/XAssets/" .. assetNameLower .. "/" .. assetNameLower .. "_load_db.h")
table.insert(result, "%{wks.location}/src/ZoneCode/Game/" .. game .. "/XAssets/" .. assetNameLower .. "/" .. assetNameLower .. "_mark_db.cpp")
table.insert(result, "%{wks.location}/src/ZoneCode/Game/" .. game .. "/XAssets/" .. assetNameLower .. "/" .. assetNameLower .. "_mark_db.h")
end end
end end

View File

@@ -145,7 +145,9 @@ namespace
LINE("") LINE("")
LINEF("#include \"{0}_load_db.h\"", Lower(m_env.m_asset->m_definition->m_name)) LINEF("#include \"{0}_load_db.h\"", Lower(m_env.m_asset->m_definition->m_name))
LINE("") LINE("")
LINEF("#include \"{0}_mark_db.h\"", Lower(m_env.m_asset->m_definition->m_name)) LINEF("#include \"Game/{0}/AssetMarker{0}.h\"", m_env.m_game)
LINE("")
LINE("#include \"Loading/AssetInfoCollector.h\"")
if (!m_env.m_referenced_assets.empty()) if (!m_env.m_referenced_assets.empty())
{ {
@@ -236,11 +238,6 @@ namespace
return std::format("Loader_{0}", asset->m_definition->m_name); return std::format("Loader_{0}", asset->m_definition->m_name);
} }
static std::string MarkerClassName(const StructureInformation* asset)
{
return std::format("Marker_{0}", asset->m_definition->m_name);
}
static std::string VariableDecl(const DataDefinition* def) static std::string VariableDecl(const DataDefinition* def)
{ {
return std::format("{0}* var{1};", def->GetFullName(), MakeSafeTypeName(def)); return std::format("{0}* var{1};", def->GetFullName(), MakeSafeTypeName(def));
@@ -2155,14 +2152,16 @@ namespace
LINE("assert(pAsset != nullptr);") LINE("assert(pAsset != nullptr);")
LINE("") LINE("")
LINEF("{0} marker(m_zone);", MarkerClassName(m_env.m_asset)) LINE("AssetInfoCollector assetInfo(m_zone);")
LINEF("AssetMarker<{0}> marker(assetInfo);", m_env.m_asset->m_asset_name)
LINE("marker.Mark(*pAsset);") LINE("marker.Mark(*pAsset);")
LINE("") LINE("")
LINEF("auto* reallocatedAsset = m_zone.Memory().Alloc<{0}>();", info->m_definition->GetFullName()) LINEF("auto* reallocatedAsset = m_zone.Memory().Alloc<{0}>();", info->m_definition->GetFullName())
LINEF("std::memcpy(reallocatedAsset, *pAsset, sizeof({0}));", info->m_definition->GetFullName()) LINEF("std::memcpy(reallocatedAsset, *pAsset, sizeof({0}));", info->m_definition->GetFullName())
LINE("") LINE("")
LINEF("m_asset_info = reinterpret_cast<XAssetInfo<{0}>*>(LinkAsset(AssetNameAccessor<{1}>()(**pAsset), reallocatedAsset, marker.GetDependencies(), " LINEF("m_asset_info = reinterpret_cast<XAssetInfo<{0}>*>(LinkAsset(AssetName<{1}>(**pAsset), reallocatedAsset, "
"marker.GetUsedScriptStrings(), marker.GetIndirectAssetReferences()));", "assetInfo.GetDependencies(), "
"assetInfo.GetUsedScriptStrings(), assetInfo.GetIndirectAssetReferences()));",
info->m_definition->GetFullName(), info->m_definition->GetFullName(),
info->m_asset_name) info->m_asset_name)
LINE("*pAsset = m_asset_info->Asset();") LINE("*pAsset = m_asset_info->Asset();")
@@ -2186,7 +2185,7 @@ namespace
LINE("") LINE("")
LINE("if (m_asset_info == nullptr && *pAsset != nullptr)") LINE("if (m_asset_info == nullptr && *pAsset != nullptr)")
m_intendation++; m_intendation++;
LINEF("m_asset_info = reinterpret_cast<XAssetInfo<{0}>*>(GetAssetInfo(AssetNameAccessor<{1}>()(**pAsset)));", LINEF("m_asset_info = reinterpret_cast<XAssetInfo<{0}>*>(GetAssetInfo(AssetName<{1}>(**pAsset)));",
m_env.m_asset->m_definition->GetFullName(), m_env.m_asset->m_definition->GetFullName(),
m_env.m_asset->m_asset_name) m_env.m_asset->m_asset_name)
m_intendation--; m_intendation--;

View File

@@ -32,14 +32,15 @@ namespace
LINE("#pragma once") LINE("#pragma once")
LINE("") LINE("")
LINEF("#include \"Game/{0}/{0}.h\"", m_env.m_game) LINEF("#include \"Game/{0}/{0}.h\"", m_env.m_game)
LINE("#include \"Loading/AssetMarker.h\"") LINE("#include \"Marking/BaseAssetMarker.h\"")
LINE("#include \"Marking/AssetVisitor.h\"")
LINE("") LINE("")
LINE("#include <string>") LINE("#include <string>")
LINE("") LINE("")
LINEF("namespace {0}", m_env.m_game) LINEF("namespace {0}", m_env.m_game)
LINE("{") LINE("{")
m_intendation++; m_intendation++;
LINEF("class {0} final : public AssetMarker", MarkerClassName(m_env.m_asset)) LINEF("class {0} final : public BaseAssetMarker", MarkerClassName(m_env.m_asset))
LINE("{") LINE("{")
m_intendation++; m_intendation++;
@@ -48,7 +49,6 @@ namespace
m_intendation++; m_intendation++;
PrintHeaderConstructor(); PrintHeaderConstructor();
PrintHeaderMainMarkMethodDeclaration(m_env.m_asset); PrintHeaderMainMarkMethodDeclaration(m_env.m_asset);
PrintHeaderGetAssetInfoMethodDeclaration(m_env.m_asset);
LINE("") LINE("")
m_intendation--; m_intendation--;
@@ -107,6 +107,8 @@ namespace
LINE("};") LINE("};")
m_intendation--; m_intendation--;
LINE("}") LINE("}")
LINE("")
LINEF("DEFINE_MARKER_CLASS_FOR_ASSET({0}::{1}, {0}::{2})", m_env.m_game, m_env.m_asset->m_asset_name, MarkerClassName(m_env.m_asset))
} }
void Source() void Source()
@@ -136,8 +138,6 @@ namespace
PrintConstructorMethod(); PrintConstructorMethod();
LINE("") LINE("")
PrintMainMarkMethod(); PrintMainMarkMethod();
LINE("")
PrintGetAssetInfoMethod();
for (const auto* type : m_env.m_used_types) for (const auto* type : m_env.m_used_types)
{ {
@@ -210,14 +210,9 @@ namespace
LINEF("void Mark_{0}();", MakeSafeTypeName(info->m_definition)) LINEF("void Mark_{0}();", MakeSafeTypeName(info->m_definition))
} }
void PrintHeaderGetAssetInfoMethodDeclaration(const StructureInformation* info) const
{
LINEF("XAssetInfo<{0}>* GetAssetInfo({0}* pAsset) const;", info->m_definition->GetFullName())
}
void PrintHeaderConstructor() const void PrintHeaderConstructor() const
{ {
LINEF("{0}(Zone& zone);", MarkerClassName(m_env.m_asset)) LINEF("explicit {0}(AssetVisitor& visitor);", MarkerClassName(m_env.m_asset))
} }
void PrintHeaderMainMarkMethodDeclaration(const StructureInformation* info) const void PrintHeaderMainMarkMethodDeclaration(const StructureInformation* info) const
@@ -237,10 +232,10 @@ namespace
void PrintConstructorMethod() void PrintConstructorMethod()
{ {
LINEF("{0}::{0}(Zone& zone)", MarkerClassName(m_env.m_asset)) LINEF("{0}::{0}(AssetVisitor& visitor)", MarkerClassName(m_env.m_asset))
m_intendation++; m_intendation++;
LINEF(": AssetMarker({0}::EnumEntry, zone)", m_env.m_asset->m_asset_name) LINE(": BaseAssetMarker(visitor)")
m_intendation--; m_intendation--;
LINE("{") LINE("{")
@@ -286,7 +281,7 @@ namespace
if (info && StructureComputations(info).IsAsset()) if (info && StructureComputations(info).IsAsset())
{ {
LINEF("AddDependency({0}(m_zone).GetAssetInfo(*{1}));", MarkerClassName(info), MakeTypePtrVarName(def)) LINEF("Mark_Dependency<{0}>(*{1});", info->m_asset_name, MakeTypePtrVarName(def))
} }
else else
{ {
@@ -413,7 +408,7 @@ namespace
{ {
if (loadType == MemberLoadType::SINGLE_POINTER) if (loadType == MemberLoadType::SINGLE_POINTER)
{ {
LINEF("AddDependency({0}(m_zone).GetAssetInfo({1}));", MarkerClassName(member->m_type), MakeMemberAccess(info, member, modifier)) LINEF("Mark_Dependency<{0}>({1});", member->m_type->m_asset_name, MakeMemberAccess(info, member, modifier))
} }
else if (loadType == MemberLoadType::POINTER_ARRAY) else if (loadType == MemberLoadType::POINTER_ARRAY)
{ {
@@ -752,20 +747,6 @@ namespace
LINE("}") LINE("}")
} }
void PrintGetAssetInfoMethod()
{
LINEF("XAssetInfo<{0}>* {1}::GetAssetInfo({0}* pAsset) const", m_env.m_asset->m_definition->GetFullName(), MarkerClassName(m_env.m_asset))
LINE("{")
m_intendation++;
LINEF("return reinterpret_cast<XAssetInfo<{0}>*>(GetAssetInfoByName(AssetNameAccessor<{1}>()(*pAsset)));",
m_env.m_asset->m_definition->GetFullName(),
m_env.m_asset->m_asset_name)
m_intendation--;
LINE("}")
}
void PrintMainMarkMethod() void PrintMainMarkMethod()
{ {
LINEF("void {0}::Mark({1}* pAsset)", MarkerClassName(m_env.m_asset), m_env.m_asset->m_definition->GetFullName()) LINEF("void {0}::Mark({1}* pAsset)", MarkerClassName(m_env.m_asset), m_env.m_asset->m_definition->GetFullName())

View File

@@ -261,7 +261,7 @@ namespace
"{0}::{0}({1}* asset, const Zone& zone, IZoneOutputStream& stream)", WriterClassName(m_env.m_asset), m_env.m_asset->m_definition->GetFullName()) "{0}::{0}({1}* asset, const Zone& zone, IZoneOutputStream& stream)", WriterClassName(m_env.m_asset), m_env.m_asset->m_definition->GetFullName())
m_intendation++; m_intendation++;
LINEF(": AssetWriter(zone.m_pools->GetAssetOrAssetReference({0}::EnumEntry, AssetNameAccessor<{0}>()(*asset)), zone, stream)", LINEF(": AssetWriter(zone.m_pools->GetAssetOrAssetReference({0}::EnumEntry, NonReferenceAssetName(AssetName<{0}>(*asset))), zone, stream)",
m_env.m_asset->m_asset_name) m_env.m_asset->m_asset_name)
m_intendation--; m_intendation--;

View File

@@ -10,6 +10,7 @@ function ZoneCommon:include(includes)
ObjCommon:include(includes) ObjCommon:include(includes)
Parser:include(includes) Parser:include(includes)
Cryptography:include(includes) Cryptography:include(includes)
ZoneCode:include(includes)
end end
end end
@@ -21,6 +22,7 @@ function ZoneCommon:link(links)
links:linkto(Parser) links:linkto(Parser)
links:linkto(Utils) links:linkto(Utils)
links:linkto(lzx) links:linkto(lzx)
ZoneCode:use()
end end
function ZoneCommon:use() function ZoneCommon:use()
@@ -43,9 +45,20 @@ function ZoneCommon:project()
files { files {
path.join(folder, "ZoneCommon/**.h"), path.join(folder, "ZoneCommon/**.h"),
path.join(folder, "ZoneCommon/**.cpp") path.join(folder, "ZoneCommon/**.cpp"),
ZoneCode:allMarkFiles()
}
vpaths {
["*"] = {
path.join(folder, "ZoneCommon"),
path.join(BuildFolder(), "src/ZoneCode")
}
} }
lzx:include(includes)
self:include(includes) self:include(includes)
lzx:include(includes)
ZoneCode:include(includes)
ZoneCode:use()
end end

View File

@@ -0,0 +1,27 @@
#pragma once
#include "Game/IW3/XAssets/clipmap_t/clipmap_t_mark_db.h"
#include "Game/IW3/XAssets/comworld/comworld_mark_db.h"
#include "Game/IW3/XAssets/font_s/font_s_mark_db.h"
#include "Game/IW3/XAssets/fxeffectdef/fxeffectdef_mark_db.h"
#include "Game/IW3/XAssets/fximpacttable/fximpacttable_mark_db.h"
#include "Game/IW3/XAssets/gameworldmp/gameworldmp_mark_db.h"
#include "Game/IW3/XAssets/gameworldsp/gameworldsp_mark_db.h"
#include "Game/IW3/XAssets/gfximage/gfximage_mark_db.h"
#include "Game/IW3/XAssets/gfxlightdef/gfxlightdef_mark_db.h"
#include "Game/IW3/XAssets/gfxworld/gfxworld_mark_db.h"
#include "Game/IW3/XAssets/loadedsound/loadedsound_mark_db.h"
#include "Game/IW3/XAssets/localizeentry/localizeentry_mark_db.h"
#include "Game/IW3/XAssets/mapents/mapents_mark_db.h"
#include "Game/IW3/XAssets/material/material_mark_db.h"
#include "Game/IW3/XAssets/materialtechniqueset/materialtechniqueset_mark_db.h"
#include "Game/IW3/XAssets/menudef_t/menudef_t_mark_db.h"
#include "Game/IW3/XAssets/menulist/menulist_mark_db.h"
#include "Game/IW3/XAssets/physpreset/physpreset_mark_db.h"
#include "Game/IW3/XAssets/rawfile/rawfile_mark_db.h"
#include "Game/IW3/XAssets/snd_alias_list_t/snd_alias_list_t_mark_db.h"
#include "Game/IW3/XAssets/sndcurve/sndcurve_mark_db.h"
#include "Game/IW3/XAssets/stringtable/stringtable_mark_db.h"
#include "Game/IW3/XAssets/weapondef/weapondef_mark_db.h"
#include "Game/IW3/XAssets/xanimparts/xanimparts_mark_db.h"
#include "Game/IW3/XAssets/xmodel/xmodel_mark_db.h"

View File

@@ -0,0 +1,37 @@
#pragma once
#include "Game/IW4/XAssets/addonmapents/addonmapents_mark_db.h"
#include "Game/IW4/XAssets/clipmap_t/clipmap_t_mark_db.h"
#include "Game/IW4/XAssets/comworld/comworld_mark_db.h"
#include "Game/IW4/XAssets/font_s/font_s_mark_db.h"
#include "Game/IW4/XAssets/fxeffectdef/fxeffectdef_mark_db.h"
#include "Game/IW4/XAssets/fximpacttable/fximpacttable_mark_db.h"
#include "Game/IW4/XAssets/fxworld/fxworld_mark_db.h"
#include "Game/IW4/XAssets/gameworldmp/gameworldmp_mark_db.h"
#include "Game/IW4/XAssets/gameworldsp/gameworldsp_mark_db.h"
#include "Game/IW4/XAssets/gfximage/gfximage_mark_db.h"
#include "Game/IW4/XAssets/gfxlightdef/gfxlightdef_mark_db.h"
#include "Game/IW4/XAssets/gfxworld/gfxworld_mark_db.h"
#include "Game/IW4/XAssets/leaderboarddef/leaderboarddef_mark_db.h"
#include "Game/IW4/XAssets/loadedsound/loadedsound_mark_db.h"
#include "Game/IW4/XAssets/localizeentry/localizeentry_mark_db.h"
#include "Game/IW4/XAssets/mapents/mapents_mark_db.h"
#include "Game/IW4/XAssets/material/material_mark_db.h"
#include "Game/IW4/XAssets/materialpixelshader/materialpixelshader_mark_db.h"
#include "Game/IW4/XAssets/materialtechniqueset/materialtechniqueset_mark_db.h"
#include "Game/IW4/XAssets/materialvertexdeclaration/materialvertexdeclaration_mark_db.h"
#include "Game/IW4/XAssets/materialvertexshader/materialvertexshader_mark_db.h"
#include "Game/IW4/XAssets/menudef_t/menudef_t_mark_db.h"
#include "Game/IW4/XAssets/menulist/menulist_mark_db.h"
#include "Game/IW4/XAssets/physcollmap/physcollmap_mark_db.h"
#include "Game/IW4/XAssets/physpreset/physpreset_mark_db.h"
#include "Game/IW4/XAssets/rawfile/rawfile_mark_db.h"
#include "Game/IW4/XAssets/snd_alias_list_t/snd_alias_list_t_mark_db.h"
#include "Game/IW4/XAssets/sndcurve/sndcurve_mark_db.h"
#include "Game/IW4/XAssets/stringtable/stringtable_mark_db.h"
#include "Game/IW4/XAssets/structureddatadefset/structureddatadefset_mark_db.h"
#include "Game/IW4/XAssets/tracerdef/tracerdef_mark_db.h"
#include "Game/IW4/XAssets/vehicledef/vehicledef_mark_db.h"
#include "Game/IW4/XAssets/weaponcompletedef/weaponcompletedef_mark_db.h"
#include "Game/IW4/XAssets/xanimparts/xanimparts_mark_db.h"
#include "Game/IW4/XAssets/xmodel/xmodel_mark_db.h"

View File

@@ -0,0 +1,42 @@
#pragma once
#include "Game/IW5/XAssets/addonmapents/addonmapents_mark_db.h"
#include "Game/IW5/XAssets/clipmap_t/clipmap_t_mark_db.h"
#include "Game/IW5/XAssets/comworld/comworld_mark_db.h"
#include "Game/IW5/XAssets/font_s/font_s_mark_db.h"
#include "Game/IW5/XAssets/fxeffectdef/fxeffectdef_mark_db.h"
#include "Game/IW5/XAssets/fximpacttable/fximpacttable_mark_db.h"
#include "Game/IW5/XAssets/fxworld/fxworld_mark_db.h"
#include "Game/IW5/XAssets/gfximage/gfximage_mark_db.h"
#include "Game/IW5/XAssets/gfxlightdef/gfxlightdef_mark_db.h"
#include "Game/IW5/XAssets/gfxworld/gfxworld_mark_db.h"
#include "Game/IW5/XAssets/glassworld/glassworld_mark_db.h"
#include "Game/IW5/XAssets/leaderboarddef/leaderboarddef_mark_db.h"
#include "Game/IW5/XAssets/loadedsound/loadedsound_mark_db.h"
#include "Game/IW5/XAssets/localizeentry/localizeentry_mark_db.h"
#include "Game/IW5/XAssets/mapents/mapents_mark_db.h"
#include "Game/IW5/XAssets/material/material_mark_db.h"
#include "Game/IW5/XAssets/materialpixelshader/materialpixelshader_mark_db.h"
#include "Game/IW5/XAssets/materialtechniqueset/materialtechniqueset_mark_db.h"
#include "Game/IW5/XAssets/materialvertexdeclaration/materialvertexdeclaration_mark_db.h"
#include "Game/IW5/XAssets/materialvertexshader/materialvertexshader_mark_db.h"
#include "Game/IW5/XAssets/menudef_t/menudef_t_mark_db.h"
#include "Game/IW5/XAssets/menulist/menulist_mark_db.h"
#include "Game/IW5/XAssets/pathdata/pathdata_mark_db.h"
#include "Game/IW5/XAssets/physcollmap/physcollmap_mark_db.h"
#include "Game/IW5/XAssets/physpreset/physpreset_mark_db.h"
#include "Game/IW5/XAssets/rawfile/rawfile_mark_db.h"
#include "Game/IW5/XAssets/scriptfile/scriptfile_mark_db.h"
#include "Game/IW5/XAssets/snd_alias_list_t/snd_alias_list_t_mark_db.h"
#include "Game/IW5/XAssets/sndcurve/sndcurve_mark_db.h"
#include "Game/IW5/XAssets/stringtable/stringtable_mark_db.h"
#include "Game/IW5/XAssets/structureddatadefset/structureddatadefset_mark_db.h"
#include "Game/IW5/XAssets/surfacefxtable/surfacefxtable_mark_db.h"
#include "Game/IW5/XAssets/tracerdef/tracerdef_mark_db.h"
#include "Game/IW5/XAssets/vehicledef/vehicledef_mark_db.h"
#include "Game/IW5/XAssets/vehicletrack/vehicletrack_mark_db.h"
#include "Game/IW5/XAssets/weaponattachment/weaponattachment_mark_db.h"
#include "Game/IW5/XAssets/weaponcompletedef/weaponcompletedef_mark_db.h"
#include "Game/IW5/XAssets/xanimparts/xanimparts_mark_db.h"
#include "Game/IW5/XAssets/xmodel/xmodel_mark_db.h"
#include "Game/IW5/XAssets/xmodelsurfs/xmodelsurfs_mark_db.h"

View File

@@ -0,0 +1,34 @@
#pragma once
#include "Game/T5/XAssets/clipmap_t/clipmap_t_mark_db.h"
#include "Game/T5/XAssets/comworld/comworld_mark_db.h"
#include "Game/T5/XAssets/ddlroot_t/ddlroot_t_mark_db.h"
#include "Game/T5/XAssets/destructibledef/destructibledef_mark_db.h"
#include "Game/T5/XAssets/emblemset/emblemset_mark_db.h"
#include "Game/T5/XAssets/font_s/font_s_mark_db.h"
#include "Game/T5/XAssets/fxeffectdef/fxeffectdef_mark_db.h"
#include "Game/T5/XAssets/fximpacttable/fximpacttable_mark_db.h"
#include "Game/T5/XAssets/gameworldmp/gameworldmp_mark_db.h"
#include "Game/T5/XAssets/gameworldsp/gameworldsp_mark_db.h"
#include "Game/T5/XAssets/gfximage/gfximage_mark_db.h"
#include "Game/T5/XAssets/gfxlightdef/gfxlightdef_mark_db.h"
#include "Game/T5/XAssets/gfxworld/gfxworld_mark_db.h"
#include "Game/T5/XAssets/glasses/glasses_mark_db.h"
#include "Game/T5/XAssets/localizeentry/localizeentry_mark_db.h"
#include "Game/T5/XAssets/mapents/mapents_mark_db.h"
#include "Game/T5/XAssets/material/material_mark_db.h"
#include "Game/T5/XAssets/materialtechniqueset/materialtechniqueset_mark_db.h"
#include "Game/T5/XAssets/menudef_t/menudef_t_mark_db.h"
#include "Game/T5/XAssets/menulist/menulist_mark_db.h"
#include "Game/T5/XAssets/packindex/packindex_mark_db.h"
#include "Game/T5/XAssets/physconstraints/physconstraints_mark_db.h"
#include "Game/T5/XAssets/physpreset/physpreset_mark_db.h"
#include "Game/T5/XAssets/rawfile/rawfile_mark_db.h"
#include "Game/T5/XAssets/sndbank/sndbank_mark_db.h"
#include "Game/T5/XAssets/snddriverglobals/snddriverglobals_mark_db.h"
#include "Game/T5/XAssets/sndpatch/sndpatch_mark_db.h"
#include "Game/T5/XAssets/stringtable/stringtable_mark_db.h"
#include "Game/T5/XAssets/weaponvariantdef/weaponvariantdef_mark_db.h"
#include "Game/T5/XAssets/xanimparts/xanimparts_mark_db.h"
#include "Game/T5/XAssets/xglobals/xglobals_mark_db.h"
#include "Game/T5/XAssets/xmodel/xmodel_mark_db.h"

View File

@@ -0,0 +1,50 @@
#pragma once
#include "Game/T6/XAssets/addonmapents/addonmapents_mark_db.h"
#include "Game/T6/XAssets/clipmap_t/clipmap_t_mark_db.h"
#include "Game/T6/XAssets/comworld/comworld_mark_db.h"
#include "Game/T6/XAssets/ddlroot_t/ddlroot_t_mark_db.h"
#include "Game/T6/XAssets/destructibledef/destructibledef_mark_db.h"
#include "Game/T6/XAssets/emblemset/emblemset_mark_db.h"
#include "Game/T6/XAssets/font_s/font_s_mark_db.h"
#include "Game/T6/XAssets/fonticon/fonticon_mark_db.h"
#include "Game/T6/XAssets/footstepfxtabledef/footstepfxtabledef_mark_db.h"
#include "Game/T6/XAssets/footsteptabledef/footsteptabledef_mark_db.h"
#include "Game/T6/XAssets/fxeffectdef/fxeffectdef_mark_db.h"
#include "Game/T6/XAssets/fximpacttable/fximpacttable_mark_db.h"
#include "Game/T6/XAssets/gameworldmp/gameworldmp_mark_db.h"
#include "Game/T6/XAssets/gameworldsp/gameworldsp_mark_db.h"
#include "Game/T6/XAssets/gfximage/gfximage_mark_db.h"
#include "Game/T6/XAssets/gfxlightdef/gfxlightdef_mark_db.h"
#include "Game/T6/XAssets/gfxworld/gfxworld_mark_db.h"
#include "Game/T6/XAssets/glasses/glasses_mark_db.h"
#include "Game/T6/XAssets/keyvaluepairs/keyvaluepairs_mark_db.h"
#include "Game/T6/XAssets/leaderboarddef/leaderboarddef_mark_db.h"
#include "Game/T6/XAssets/localizeentry/localizeentry_mark_db.h"
#include "Game/T6/XAssets/mapents/mapents_mark_db.h"
#include "Game/T6/XAssets/material/material_mark_db.h"
#include "Game/T6/XAssets/materialtechniqueset/materialtechniqueset_mark_db.h"
#include "Game/T6/XAssets/memoryblock/memoryblock_mark_db.h"
#include "Game/T6/XAssets/menudef_t/menudef_t_mark_db.h"
#include "Game/T6/XAssets/menulist/menulist_mark_db.h"
#include "Game/T6/XAssets/physconstraints/physconstraints_mark_db.h"
#include "Game/T6/XAssets/physpreset/physpreset_mark_db.h"
#include "Game/T6/XAssets/qdb/qdb_mark_db.h"
#include "Game/T6/XAssets/rawfile/rawfile_mark_db.h"
#include "Game/T6/XAssets/scriptparsetree/scriptparsetree_mark_db.h"
#include "Game/T6/XAssets/skinnedvertsdef/skinnedvertsdef_mark_db.h"
#include "Game/T6/XAssets/slug/slug_mark_db.h"
#include "Game/T6/XAssets/sndbank/sndbank_mark_db.h"
#include "Game/T6/XAssets/snddriverglobals/snddriverglobals_mark_db.h"
#include "Game/T6/XAssets/sndpatch/sndpatch_mark_db.h"
#include "Game/T6/XAssets/stringtable/stringtable_mark_db.h"
#include "Game/T6/XAssets/tracerdef/tracerdef_mark_db.h"
#include "Game/T6/XAssets/vehicledef/vehicledef_mark_db.h"
#include "Game/T6/XAssets/weaponattachment/weaponattachment_mark_db.h"
#include "Game/T6/XAssets/weaponattachmentunique/weaponattachmentunique_mark_db.h"
#include "Game/T6/XAssets/weaponcamo/weaponcamo_mark_db.h"
#include "Game/T6/XAssets/weaponvariantdef/weaponvariantdef_mark_db.h"
#include "Game/T6/XAssets/xanimparts/xanimparts_mark_db.h"
#include "Game/T6/XAssets/xglobals/xglobals_mark_db.h"
#include "Game/T6/XAssets/xmodel/xmodel_mark_db.h"
#include "Game/T6/XAssets/zbarrierdef/zbarrierdef_mark_db.h"

View File

@@ -0,0 +1,29 @@
#pragma once
#include "Pool/XAssetInfo.h"
#include "Zone/ZoneTypes.h"
#include <optional>
class AssetVisitor
{
public:
virtual ~AssetVisitor() = default;
virtual std::optional<XAssetInfoGeneric*> Visit_Dependency(asset_type_t assetType, const char* assetName)
{
// Do nothing by default
return std::nullopt;
}
virtual std::optional<scr_string_t> Visit_ScriptString(scr_string_t scriptString)
{
// Do nothing by default
return std::nullopt;
}
virtual void Visit_IndirectAssetRef(asset_type_t assetType, const char* assetName)
{
// Do nothing by default
}
};

View File

@@ -0,0 +1,40 @@
#include "BaseAssetMarker.h"
#include <algorithm>
#include <cassert>
BaseAssetMarker::BaseAssetMarker(AssetVisitor& visitor)
: m_visitor(visitor)
{
}
void BaseAssetMarker::Mark_ScriptString(scr_string_t& scriptString) const
{
const auto result = m_visitor.Visit_ScriptString(scriptString);
if (result.has_value())
scriptString = *result;
}
void BaseAssetMarker::MarkArray_ScriptString(scr_string_t* scriptStringArray, const size_t count) const
{
assert(scriptStringArray != nullptr);
for (size_t index = 0; index < count; index++)
Mark_ScriptString(scriptStringArray[index]);
}
void BaseAssetMarker::Mark_IndirectAssetRef(const asset_type_t assetType, const char* assetName) const
{
if (!assetName)
return;
m_visitor.Visit_IndirectAssetRef(assetType, assetName);
}
void BaseAssetMarker::MarkArray_IndirectAssetRef(const asset_type_t assetType, const char** assetNames, const size_t count) const
{
assert(assetNames != nullptr);
for (size_t index = 0; index < count; index++)
Mark_IndirectAssetRef(assetType, assetNames[index]);
}

View File

@@ -0,0 +1,45 @@
#pragma once
#include "Game/IAsset.h"
#include "Marking/AssetVisitor.h"
#include "Zone/ZoneTypes.h"
#include <type_traits>
class BaseAssetMarker
{
protected:
explicit BaseAssetMarker(AssetVisitor& visitor);
template<typename AssetType> void Mark_Dependency(std::add_lvalue_reference_t<std::add_pointer_t<typename AssetType::Type>> asset)
{
static_assert(std::is_base_of_v<IAssetBase, AssetType>);
const auto result = m_visitor.Visit_Dependency(AssetType::EnumEntry, AssetName<AssetType>(*asset));
if (result.has_value())
asset = static_cast<std::add_pointer_t<typename AssetType::Type>>((*result)->m_ptr);
}
void Mark_ScriptString(scr_string_t& scriptString) const;
void MarkArray_ScriptString(scr_string_t* scriptStringArray, size_t count) const;
void Mark_IndirectAssetRef(asset_type_t assetType, const char* assetName) const;
void MarkArray_IndirectAssetRef(asset_type_t assetType, const char** assetNames, size_t count) const;
AssetVisitor& m_visitor;
};
template<typename AssetType> struct AssetMarkerWrapper
{
static_assert(std::is_base_of_v<IAssetBase, AssetType>);
// using WrapperClass = WrapperClass;
};
template<typename AssetType> using AssetMarker = AssetMarkerWrapper<AssetType>::WrapperClass;
#define DEFINE_MARKER_CLASS_FOR_ASSET(asset, markerClass) \
template<> struct AssetMarkerWrapper<asset> \
{ \
static_assert(std::is_base_of_v<IAssetBase, asset>); \
using WrapperClass = markerClass; \
};

View File

@@ -0,0 +1,85 @@
#include "AssetInfoCollector.h"
#include <algorithm>
#include <cassert>
AssetInfoCollector::AssetInfoCollector(Zone& zone)
: m_zone(zone)
{
}
std::vector<XAssetInfoGeneric*> AssetInfoCollector::GetDependencies() const
{
std::vector<XAssetInfoGeneric*> dependencies;
if (!m_dependencies.empty())
{
dependencies.reserve(m_dependencies.size());
for (auto dependency : m_dependencies)
dependencies.push_back(dependency);
}
return dependencies;
}
std::vector<scr_string_t> AssetInfoCollector::GetUsedScriptStrings() const
{
std::vector<scr_string_t> usedScriptStrings;
if (!m_used_script_strings.empty())
{
usedScriptStrings.reserve(m_used_script_strings.size());
for (auto scrString : m_used_script_strings)
usedScriptStrings.push_back(scrString);
std::ranges::sort(usedScriptStrings);
}
return usedScriptStrings;
}
std::vector<IndirectAssetReference> AssetInfoCollector::GetIndirectAssetReferences() const
{
std::vector<IndirectAssetReference> assetReferences;
if (!m_indirect_asset_references.empty())
{
assetReferences.reserve(m_indirect_asset_references.size());
for (const auto& assetReference : m_indirect_asset_references)
assetReferences.emplace_back(assetReference);
}
return assetReferences;
}
std::optional<XAssetInfoGeneric*> AssetInfoCollector::Visit_Dependency(const asset_type_t assetType, const char* assetName)
{
auto* assetInfo = m_zone.m_pools->GetAsset(assetType, assetName);
if (assetInfo == nullptr)
return std::nullopt;
const auto existingEntry = m_dependencies.find(assetInfo);
if (existingEntry != m_dependencies.end())
return std::nullopt;
m_dependencies.emplace(assetInfo);
return std::nullopt;
}
std::optional<scr_string_t> AssetInfoCollector::Visit_ScriptString(scr_string_t scriptString)
{
assert(scriptString < m_zone.m_script_strings.Count());
if (scriptString >= m_zone.m_script_strings.Count())
return std::nullopt;
m_used_script_strings.emplace(scriptString);
return std::nullopt;
}
void AssetInfoCollector::Visit_IndirectAssetRef(asset_type_t assetType, const char* assetName)
{
if (!assetName || !assetName[0])
return;
m_indirect_asset_references.emplace(assetType, assetName);
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include "Marking/AssetVisitor.h"
#include "Pool/XAssetInfo.h"
#include "Zone/ZoneTypes.h"
#include <optional>
#include <unordered_set>
#include <vector>
class AssetInfoCollector : public AssetVisitor
{
public:
explicit AssetInfoCollector(Zone& zone);
~AssetInfoCollector() override = default;
[[nodiscard]] std::vector<XAssetInfoGeneric*> GetDependencies() const;
[[nodiscard]] std::vector<scr_string_t> GetUsedScriptStrings() const;
[[nodiscard]] std::vector<IndirectAssetReference> GetIndirectAssetReferences() const;
std::optional<XAssetInfoGeneric*> Visit_Dependency(asset_type_t assetType, const char* assetName) override;
std::optional<scr_string_t> Visit_ScriptString(scr_string_t scriptString) override;
void Visit_IndirectAssetRef(asset_type_t assetType, const char* assetName) override;
private:
std::unordered_set<XAssetInfoGeneric*> m_dependencies;
std::unordered_set<scr_string_t> m_used_script_strings;
std::unordered_set<IndirectAssetReference> m_indirect_asset_references;
Zone& m_zone;
};

View File

@@ -1,102 +0,0 @@
#include "AssetMarker.h"
#include <algorithm>
#include <cassert>
AssetMarker::AssetMarker(const asset_type_t assetType, Zone& zone)
: m_zone(zone),
m_asset_type(assetType)
{
}
void AssetMarker::AddDependency(XAssetInfoGeneric* assetInfo)
{
if (assetInfo == nullptr)
return;
const auto existingEntry = m_dependencies.find(assetInfo);
if (existingEntry != m_dependencies.end())
return;
m_dependencies.emplace(assetInfo);
}
void AssetMarker::Mark_ScriptString(const scr_string_t scrString)
{
assert(scrString < m_zone.m_script_strings.Count());
if (scrString >= m_zone.m_script_strings.Count())
return;
m_used_script_strings.emplace(scrString);
}
void AssetMarker::MarkArray_ScriptString(const scr_string_t* scrStringArray, const size_t count)
{
assert(scrStringArray != nullptr);
for (size_t index = 0; index < count; index++)
Mark_ScriptString(scrStringArray[index]);
}
void AssetMarker::Mark_IndirectAssetRef(asset_type_t type, const char* assetRefName)
{
if (!assetRefName || !assetRefName[0])
return;
m_indirect_asset_references.emplace(type, assetRefName);
}
void AssetMarker::MarkArray_IndirectAssetRef(const asset_type_t type, const char** assetRefNames, const size_t count)
{
assert(assetRefNames != nullptr);
for (size_t index = 0; index < count; index++)
Mark_IndirectAssetRef(type, assetRefNames[index]);
}
XAssetInfoGeneric* AssetMarker::GetAssetInfoByName(const std::string& name) const
{
return m_zone.m_pools->GetAsset(m_asset_type, name);
}
std::vector<XAssetInfoGeneric*> AssetMarker::GetDependencies() const
{
std::vector<XAssetInfoGeneric*> dependencies;
if (!m_dependencies.empty())
{
dependencies.reserve(m_dependencies.size());
for (auto dependency : m_dependencies)
dependencies.push_back(dependency);
}
return dependencies;
}
std::vector<scr_string_t> AssetMarker::GetUsedScriptStrings() const
{
std::vector<scr_string_t> usedScriptStrings;
if (!m_used_script_strings.empty())
{
usedScriptStrings.reserve(m_used_script_strings.size());
for (auto scrString : m_used_script_strings)
usedScriptStrings.push_back(scrString);
std::ranges::sort(usedScriptStrings);
}
return usedScriptStrings;
}
std::vector<IndirectAssetReference> AssetMarker::GetIndirectAssetReferences() const
{
std::vector<IndirectAssetReference> assetReferences;
if (!m_indirect_asset_references.empty())
{
assetReferences.reserve(m_indirect_asset_references.size());
for (const auto& assetReference : m_indirect_asset_references)
assetReferences.emplace_back(assetReference);
}
return assetReferences;
}

View File

@@ -1,37 +0,0 @@
#pragma once
#include "ContentLoaderBase.h"
#include "Pool/XAssetInfo.h"
#include "Zone/ZoneTypes.h"
#include <unordered_set>
class AssetMarker
{
public:
[[nodiscard]] std::vector<XAssetInfoGeneric*> GetDependencies() const;
[[nodiscard]] std::vector<scr_string_t> GetUsedScriptStrings() const;
[[nodiscard]] std::vector<IndirectAssetReference> GetIndirectAssetReferences() const;
protected:
AssetMarker(asset_type_t assetType, Zone& zone);
void AddDependency(XAssetInfoGeneric* assetInfo);
void Mark_ScriptString(scr_string_t scrString);
void MarkArray_ScriptString(const scr_string_t* scrStringArray, size_t count);
void Mark_IndirectAssetRef(asset_type_t type, const char* assetRefName);
void MarkArray_IndirectAssetRef(asset_type_t type, const char** assetRefNames, size_t count);
[[nodiscard]] XAssetInfoGeneric* GetAssetInfoByName(const std::string& name) const;
Zone& m_zone;
private:
asset_type_t m_asset_type;
std::unordered_set<XAssetInfoGeneric*> m_dependencies;
std::unordered_set<scr_string_t> m_used_script_strings;
std::unordered_set<IndirectAssetReference> m_indirect_asset_references;
};

View File

@@ -10,6 +10,14 @@ AssetWriter::AssetWriter(XAssetInfoGeneric* asset, const Zone& zone, IZoneOutput
{ {
} }
const char* AssetWriter::NonReferenceAssetName(const char* assetName)
{
if (assetName && assetName[0] == ',')
return &assetName[1];
return assetName;
}
scr_string_t AssetWriter::UseScriptString(const scr_string_t scrString) const scr_string_t AssetWriter::UseScriptString(const scr_string_t scrString) const
{ {
assert(scrString < m_asset->m_zone->m_script_strings.Count()); assert(scrString < m_asset->m_zone->m_script_strings.Count());
@@ -17,6 +25,7 @@ scr_string_t AssetWriter::UseScriptString(const scr_string_t scrString) const
if (m_asset->m_zone == &m_zone) if (m_asset->m_zone == &m_zone)
return scrString; return scrString;
// The asset comes from a different zone, we need to translate it
const auto strValue = m_asset->m_zone->m_script_strings.CValue(scrString); const auto strValue = m_asset->m_zone->m_script_strings.CValue(scrString);
return m_zone.m_script_strings.GetScriptString(strValue); return m_zone.m_script_strings.GetScriptString(strValue);
} }

View File

@@ -11,7 +11,8 @@ class AssetWriter : public ContentWriterBase
protected: protected:
AssetWriter(XAssetInfoGeneric* asset, const Zone& zone, IZoneOutputStream& stream); AssetWriter(XAssetInfoGeneric* asset, const Zone& zone, IZoneOutputStream& stream);
_NODISCARD scr_string_t UseScriptString(scr_string_t scrString) const; [[nodiscard]] static const char* NonReferenceAssetName(const char* assetName);
[[nodiscard]] scr_string_t UseScriptString(scr_string_t scrString) const;
void WriteScriptStringArray(bool atStreamStart, size_t count); void WriteScriptStringArray(bool atStreamStart, size_t count);
XAssetInfoGeneric* m_asset; XAssetInfoGeneric* m_asset;

View File

@@ -11,6 +11,7 @@ end
function Catch2Common:link(links) function Catch2Common:link(links)
links:add(self:name()) links:add(self:name())
links:linkto(catch2) links:linkto(catch2)
links:linkto(Utils)
end end
function Catch2Common:use() function Catch2Common:use()
@@ -45,7 +46,9 @@ function Catch2Common:project()
self:include(includes) self:include(includes)
catch2:include(includes) catch2:include(includes)
Utils:include(includes)
links:linkto(catch2) links:linkto(catch2)
links:linkto(Utils)
links:linkall() links:linkall()
end end

View File

@@ -24,4 +24,13 @@ namespace oat::paths
return result; return result;
} }
std::filesystem::path GetTempDirectory(const std::string& subDir)
{
auto result = fs::current_path() / "build" / ".tmp" / subDir;
if (!fs::is_directory(result))
fs::create_directories(result);
return result;
}
} // namespace oat::paths } // namespace oat::paths

View File

@@ -1,10 +1,12 @@
#pragma once #pragma once
#include <filesystem> #include <filesystem>
#include <string>
namespace oat::paths namespace oat::paths
{ {
std::filesystem::path GetSourceDirectory(); std::filesystem::path GetSourceDirectory();
std::filesystem::path GetTestDirectory(); std::filesystem::path GetTestDirectory();
std::filesystem::path GetTempDirectory(); std::filesystem::path GetTempDirectory();
std::filesystem::path GetTempDirectory(const std::string& subDir);
} // namespace oat::paths } // namespace oat::paths

View File

@@ -1,12 +1,28 @@
#include "Utils/Logging/Log.h"
#include <catch2/catch_session.hpp> #include <catch2/catch_session.hpp>
#include <cstdlib>
#include <filesystem> #include <filesystem>
#include <format> #include <format>
#include <iostream> #include <iostream>
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace
{
bool ShouldClearTempFolder()
{
auto* envVar = std::getenv("OAT_KEEP_TMP_DIR");
return !envVar || !envVar[0] || (envVar[0] == '0' && !envVar[1]);
}
} // namespace
int main(const int argc, char* argv[]) int main(const int argc, char* argv[])
{ {
con::init();
con::set_log_level(con::LogLevel::DEBUG);
const fs::path absoluteBinDir(fs::canonical(argv[0]).parent_path()); const fs::path absoluteBinDir(fs::canonical(argv[0]).parent_path());
const auto expectedLibDir = absoluteBinDir.parent_path().parent_path(); const auto expectedLibDir = absoluteBinDir.parent_path().parent_path();
@@ -15,24 +31,24 @@ int main(const int argc, char* argv[])
if (absoluteBinDir.filename() != "tests" || expectedLibDir.filename() != "lib" || expectedBuildDir.filename() != "build") if (absoluteBinDir.filename() != "tests" || expectedLibDir.filename() != "lib" || expectedBuildDir.filename() != "build")
{ {
std::cerr << std::format("Expected test binary to be in the folder it was compiled into (build/lib/?/tests) but was {}\n", absoluteBinDir.string()); con::error("Expected test binary to be in the folder it was compiled into (build/lib/?/tests) but was {}", absoluteBinDir.string());
std::cerr << "Please do not move test executable out of compilation folder\n"; con::error("Please do not move test executable out of compilation folder");
return 1; return 1;
} }
const auto expectedSrcDir = expectedRootDir / "src"; const auto expectedSrcDir = expectedRootDir / "src";
if (!fs::is_directory(expectedSrcDir)) if (!fs::is_directory(expectedSrcDir))
{ {
std::cerr << std::format("Expected source directory to exist in {}, but it did not\n", expectedSrcDir.string()); con::error("Expected source directory to exist in {}, but it did not", expectedSrcDir.string());
std::cerr << "Please do not move test executable out of compilation folder\n"; con::error("Please do not move test executable out of compilation folder");
return 1; return 1;
} }
const auto expectedTestDir = expectedRootDir / "test"; const auto expectedTestDir = expectedRootDir / "test";
if (!fs::is_directory(expectedTestDir)) if (!fs::is_directory(expectedTestDir))
{ {
std::cerr << std::format("Expected test directory to exist in {}, but it did not\n", expectedTestDir.string()); con::error("Expected test directory to exist in {}, but it did not", expectedTestDir.string());
std::cerr << "Please do not move test executable out of compilation folder\n"; con::error("Please do not move test executable out of compilation folder");
return 1; return 1;
} }
@@ -41,8 +57,16 @@ int main(const int argc, char* argv[])
const auto result = Catch::Session().run(argc, argv); const auto result = Catch::Session().run(argc, argv);
const auto tempDir = expectedBuildDir / ".tmp"; const auto tempDir = expectedBuildDir / ".tmp";
if (ShouldClearTempFolder())
{
con::info("Clearing tmp folder. Define env var OAT_KEEP_TMP_DIR=1 to keep it.");
if (fs::is_directory(tempDir)) if (fs::is_directory(tempDir))
fs::remove_all(tempDir); fs::remove_all(tempDir);
}
else
{
con::info("Kept tmp dir {} on disk.", tempDir.string());
}
return result; return result;
} }

64
test/SystemTests.lua Normal file
View File

@@ -0,0 +1,64 @@
SystemTests = {}
function SystemTests:include(includes)
if includes:handle(self:name()) then
includedirs {
path.join(TestFolder(), "SystemTests")
}
end
end
function SystemTests:link(links)
end
function SystemTests:use()
end
function SystemTests:name()
return "SystemTests"
end
function SystemTests:project()
local folder = TestFolder()
local includes = Includes:create()
local links = Links:create()
project(self:name())
targetdir(TargetDirectoryTest)
location "%{wks.location}/test/%{prj.name}"
kind "ConsoleApp"
language "C++"
files {
path.join(folder, "SystemTests/**.h"),
path.join(folder, "SystemTests/**.cpp")
}
self:include(includes)
Catch2Common:include(includes)
Utils:include(includes)
ZoneLoading:include(includes)
ZoneWriting:include(includes)
ObjLoading:include(includes)
ObjCompiling:include(includes)
ObjWriting:include(includes)
Linking:include(includes)
Unlinking:include(includes)
catch2:include(includes)
Raw:use()
links:linkto(Utils)
links:linkto(ZoneLoading)
links:linkto(ZoneWriting)
links:linkto(ObjLoading)
links:linkto(ObjCompiling)
links:linkto(ObjWriting)
links:linkto(catch2)
links:linkto(Catch2Common)
links:linkto(Linking)
links:linkto(Unlinking)
links:linkall()
end

View File

@@ -0,0 +1 @@
This is a simple zone.

View File

@@ -0,0 +1,4 @@
>game,IW3
rawfile,SimpleZone.txt

View File

@@ -0,0 +1,65 @@
#include "Game/IW3/GameAssetPoolIW3.h"
#include "Linker.h"
#include "OatTestPaths.h"
#include "SystemTestsPaths.h"
#include "ZoneLoading.h"
#include <catch2/catch_test_macros.hpp>
#include <filesystem>
#include <format>
#include <memory>
#include <string>
namespace fs = std::filesystem;
using namespace std::literals;
namespace
{
TEST_CASE("Simple Zone(IW3)", "[iw3][system][simple]")
{
const auto assetSearchPath = (oat::paths::GetSystemTestsDirectory() / "Game/IW3/Simple").string();
const auto sourceSearchPath = (oat::paths::GetSystemTestsDirectory() / "Game/IW3/Simple").string();
const auto outputPath = oat::paths::GetTempDirectory("SimpleZoneIW3").string();
const char* argStrings[]{
"SystemTests", // bin
"--verbose",
"--asset-search-path",
assetSearchPath.c_str(),
"--source-search-path",
sourceSearchPath.c_str(),
"--output-folder",
outputPath.c_str(),
"SimpleZoneIW3",
};
LinkerArgs args;
bool shouldContinue = true;
const auto couldParseArgs = args.ParseArgs(std::extent_v<decltype(argStrings)>, argStrings, shouldContinue);
REQUIRE(couldParseArgs);
REQUIRE(shouldContinue);
const auto linker = Linker::Create(std::move(args));
const auto linkerResult = linker->Start();
REQUIRE(linkerResult);
// x64 for now produces invalid zones, don't try to load them yet
#ifdef ARCH_x86
const auto expectedZonePath = (fs::path(outputPath) / "SimpleZoneIW3.ff").string();
auto maybeZone = ZoneLoading::LoadZone(expectedZonePath, std::nullopt);
REQUIRE(maybeZone);
auto zone = std::move(*maybeZone);
auto pools = dynamic_cast<GameAssetPoolIW3*>(zone->m_pools.get());
REQUIRE(zone->m_game_id == GameId::IW3);
REQUIRE(zone->m_platform == GamePlatform::PC);
REQUIRE(zone->m_name == "SimpleZoneIW3");
REQUIRE(pools->GetTotalAssetCount() == 1);
REQUIRE(pools->m_raw_file->GetAsset("SimpleZone.txt"));
#endif
}
} // namespace

View File

@@ -0,0 +1 @@
This is a simple zone.

View File

@@ -0,0 +1,4 @@
>game,IW4
rawfile,SimpleZone.txt

View File

@@ -0,0 +1,65 @@
#include "Game/IW4/GameAssetPoolIW4.h"
#include "Linker.h"
#include "OatTestPaths.h"
#include "SystemTestsPaths.h"
#include "ZoneLoading.h"
#include <catch2/catch_test_macros.hpp>
#include <filesystem>
#include <format>
#include <memory>
#include <string>
namespace fs = std::filesystem;
using namespace std::literals;
namespace
{
TEST_CASE("Simple Zone(IW4)", "[iw4][system][simple]")
{
const auto assetSearchPath = (oat::paths::GetSystemTestsDirectory() / "Game/IW4/Simple").string();
const auto sourceSearchPath = (oat::paths::GetSystemTestsDirectory() / "Game/IW4/Simple").string();
const auto outputPath = oat::paths::GetTempDirectory("SimpleZoneIW4").string();
const char* argStrings[]{
"SystemTests", // bin
"--verbose",
"--asset-search-path",
assetSearchPath.c_str(),
"--source-search-path",
sourceSearchPath.c_str(),
"--output-folder",
outputPath.c_str(),
"SimpleZoneIW4",
};
LinkerArgs args;
bool shouldContinue = true;
const auto couldParseArgs = args.ParseArgs(std::extent_v<decltype(argStrings)>, argStrings, shouldContinue);
REQUIRE(couldParseArgs);
REQUIRE(shouldContinue);
const auto linker = Linker::Create(std::move(args));
const auto linkerResult = linker->Start();
REQUIRE(linkerResult);
// x64 for now produces invalid zones, don't try to load them yet
#ifdef ARCH_x86
const auto expectedZonePath = (fs::path(outputPath) / "SimpleZoneIW4.ff").string();
auto maybeZone = ZoneLoading::LoadZone(expectedZonePath, std::nullopt);
REQUIRE(maybeZone);
auto zone = std::move(*maybeZone);
auto pools = dynamic_cast<GameAssetPoolIW4*>(zone->m_pools.get());
REQUIRE(zone->m_game_id == GameId::IW4);
REQUIRE(zone->m_platform == GamePlatform::PC);
REQUIRE(zone->m_name == "SimpleZoneIW4");
REQUIRE(pools->GetTotalAssetCount() == 1);
REQUIRE(pools->m_raw_file->GetAsset("SimpleZone.txt"));
#endif
}
} // namespace

View File

@@ -0,0 +1 @@
This is a simple zone.

View File

@@ -0,0 +1,4 @@
>game,IW5
rawfile,SimpleZone.txt

View File

@@ -0,0 +1,65 @@
#include "Game/IW5/GameAssetPoolIW5.h"
#include "Linker.h"
#include "OatTestPaths.h"
#include "SystemTestsPaths.h"
#include "ZoneLoading.h"
#include <catch2/catch_test_macros.hpp>
#include <filesystem>
#include <format>
#include <memory>
#include <string>
namespace fs = std::filesystem;
using namespace std::literals;
namespace
{
TEST_CASE("Simple Zone(IW5)", "[iw5][system][simple]")
{
const auto assetSearchPath = (oat::paths::GetSystemTestsDirectory() / "Game/IW5/Simple").string();
const auto sourceSearchPath = (oat::paths::GetSystemTestsDirectory() / "Game/IW5/Simple").string();
const auto outputPath = oat::paths::GetTempDirectory("SimpleZoneIW5").string();
const char* argStrings[]{
"SystemTests", // bin
"--verbose",
"--asset-search-path",
assetSearchPath.c_str(),
"--source-search-path",
sourceSearchPath.c_str(),
"--output-folder",
outputPath.c_str(),
"SimpleZoneIW5",
};
LinkerArgs args;
bool shouldContinue = true;
const auto couldParseArgs = args.ParseArgs(std::extent_v<decltype(argStrings)>, argStrings, shouldContinue);
REQUIRE(couldParseArgs);
REQUIRE(shouldContinue);
const auto linker = Linker::Create(std::move(args));
const auto linkerResult = linker->Start();
REQUIRE(linkerResult);
// x64 for now produces invalid zones, don't try to load them yet
#ifdef ARCH_x86
const auto expectedZonePath = (fs::path(outputPath) / "SimpleZoneIW5.ff").string();
auto maybeZone = ZoneLoading::LoadZone(expectedZonePath, std::nullopt);
REQUIRE(maybeZone);
auto zone = std::move(*maybeZone);
auto pools = dynamic_cast<GameAssetPoolIW5*>(zone->m_pools.get());
REQUIRE(zone->m_game_id == GameId::IW5);
REQUIRE(zone->m_platform == GamePlatform::PC);
REQUIRE(zone->m_name == "SimpleZoneIW5");
REQUIRE(pools->GetTotalAssetCount() == 1);
REQUIRE(pools->m_raw_file->GetAsset("SimpleZone.txt"));
#endif
}
} // namespace

View File

@@ -0,0 +1 @@
This is a simple zone.

View File

@@ -0,0 +1,4 @@
>game,T5
rawfile,SimpleZone.txt

View File

@@ -0,0 +1,65 @@
#include "Game/T5/GameAssetPoolT5.h"
#include "Linker.h"
#include "OatTestPaths.h"
#include "SystemTestsPaths.h"
#include "ZoneLoading.h"
#include <catch2/catch_test_macros.hpp>
#include <filesystem>
#include <format>
#include <memory>
#include <string>
namespace fs = std::filesystem;
using namespace std::literals;
namespace
{
TEST_CASE("Simple Zone(T5)", "[t5][system][simple]")
{
const auto assetSearchPath = (oat::paths::GetSystemTestsDirectory() / "Game/T5/Simple").string();
const auto sourceSearchPath = (oat::paths::GetSystemTestsDirectory() / "Game/T5/Simple").string();
const auto outputPath = oat::paths::GetTempDirectory("SimpleZoneT5").string();
const char* argStrings[]{
"SystemTests", // bin
"--verbose",
"--asset-search-path",
assetSearchPath.c_str(),
"--source-search-path",
sourceSearchPath.c_str(),
"--output-folder",
outputPath.c_str(),
"SimpleZoneT5",
};
LinkerArgs args;
bool shouldContinue = true;
const auto couldParseArgs = args.ParseArgs(std::extent_v<decltype(argStrings)>, argStrings, shouldContinue);
REQUIRE(couldParseArgs);
REQUIRE(shouldContinue);
const auto linker = Linker::Create(std::move(args));
const auto linkerResult = linker->Start();
REQUIRE(linkerResult);
// x64 for now produces invalid zones, don't try to load them yet
#ifdef ARCH_x86
const auto expectedZonePath = (fs::path(outputPath) / "SimpleZoneT5.ff").string();
auto maybeZone = ZoneLoading::LoadZone(expectedZonePath, std::nullopt);
REQUIRE(maybeZone);
auto zone = std::move(*maybeZone);
auto pools = dynamic_cast<GameAssetPoolT5*>(zone->m_pools.get());
REQUIRE(zone->m_game_id == GameId::T5);
REQUIRE(zone->m_platform == GamePlatform::PC);
REQUIRE(zone->m_name == "SimpleZoneT5");
REQUIRE(pools->GetTotalAssetCount() == 1);
REQUIRE(pools->m_raw_file->GetAsset("SimpleZone.txt"));
#endif
}
} // namespace

View File

@@ -0,0 +1,4 @@
>game,T6
techniqueset,trivial_floatz_2992w610
material,test

View File

@@ -0,0 +1,4 @@
>game,T6
ignore,ZoneWithTechsetT6
material,test

View File

@@ -0,0 +1,3 @@
>game,T6
techniqueset,trivial_floatz_2992w610

View File

@@ -0,0 +1,77 @@
{
"$schema": "http://openassettools.dev/schema/material.v1.json",
"_game": "t6",
"_type": "material",
"_version": 1,
"cameraRegion": "none",
"constants": [],
"contents": 1,
"gameFlags": [],
"layeredSurfaceTypes": 536870912,
"sortKey": 4,
"stateBits": [
{
"alphaTest": "disabled",
"blendOpAlpha": "disabled",
"blendOpRgb": "disabled",
"colorWriteAlpha": true,
"colorWriteRgb": true,
"cullFace": "back",
"depthTest": "disabled",
"depthWrite": false,
"dstBlendAlpha": "zero",
"dstBlendRgb": "zero",
"polygonOffset": "offset0",
"polymodeLine": false,
"srcBlendAlpha": "one",
"srcBlendRgb": "one"
}
],
"stateBitsEntry": [
-1,
-1,
0,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1
],
"stateFlags": 0,
"surfaceFlags": 0,
"surfaceTypeBits": 0,
"techniqueSet": "trivial_floatz_2992w610",
"textureAtlas": {
"columns": 1,
"rows": 1
},
"textures": []
}

View File

@@ -0,0 +1,130 @@
/*
This makes sure that when reusing assets from an existing zone, asset dependencies that are references can be overwritten to be non-references.
In this case:
- The zone `ZoneWithTechsetT6` contains the techniqueset `trivial_floatz_2992w610`
- The zone `ZoneWithMaterialT6` contains the material `test` and with a reference to its techniqueset `,trivial_floatz_2992w610`
- The final zone `CombinedZoneT6` is built while loading both of the other fastfile and is expected to contain both the material `test` with a reference to a
(actual asset, not a reference) techniqueset `trivial_floatz_2992w610`
*/
#include "Game/T6/GameAssetPoolT6.h"
#include "Linker.h"
#include "OatTestPaths.h"
#include "SystemTestsPaths.h"
#include "ZoneLoading.h"
#include <catch2/catch_test_macros.hpp>
#include <filesystem>
#include <format>
#include <memory>
#include <string>
namespace fs = std::filesystem;
using namespace std::literals;
namespace
{
void BuildZoneWithMaterial(const fs::path& testDir, const fs::path& outputPath)
{
const auto testDirStr = testDir.string();
const auto outputPathStr = outputPath.string();
const char* argStrings[]{
"SystemTests", // bin
"--verbose",
"--asset-search-path",
testDirStr.c_str(),
"--source-search-path",
testDirStr.c_str(),
"--output-folder",
outputPathStr.c_str(),
"ZoneWithMaterialT6",
};
LinkerArgs args;
bool shouldContinue = true;
const auto couldParseArgs = args.ParseArgs(std::extent_v<decltype(argStrings)>, argStrings, shouldContinue);
REQUIRE(couldParseArgs);
REQUIRE(shouldContinue);
const auto linker = Linker::Create(std::move(args));
const auto linkerResult = linker->Start();
REQUIRE(linkerResult);
}
void BuildCombinedZone(const fs::path& testDir, const fs::path& outputPath)
{
const auto testDirStr = testDir.string();
const auto outputPathStr = outputPath.string();
const auto zoneWithTechsetPathStr = (testDir / "ZoneWithTechsetT6.ff").string();
const auto zoneWithMaterialPathStr = (outputPath / "ZoneWithMaterialT6.ff").string();
const char* argStrings[]{
"SystemTests", // bin
"--verbose",
"--load",
zoneWithTechsetPathStr.c_str(),
"--load",
zoneWithMaterialPathStr.c_str(),
"--source-search-path",
testDirStr.c_str(),
"--output-folder",
outputPathStr.c_str(),
"CombinedZoneT6",
};
LinkerArgs args;
bool shouldContinue = true;
const auto couldParseArgs = args.ParseArgs(std::extent_v<decltype(argStrings)>, argStrings, shouldContinue);
REQUIRE(couldParseArgs);
REQUIRE(shouldContinue);
const auto linker = Linker::Create(std::move(args));
const auto linkerResult = linker->Start();
REQUIRE(linkerResult);
}
void CheckCombinedZoneContent(const fs::path& outputPath)
{
const auto expectedZonePath = (fs::path(outputPath) / "CombinedZoneT6.ff").string();
auto maybeZone = ZoneLoading::LoadZone(expectedZonePath, std::nullopt);
REQUIRE(maybeZone);
auto zone = std::move(*maybeZone);
auto pools = dynamic_cast<GameAssetPoolT6*>(zone->m_pools.get());
REQUIRE(zone->m_game_id == GameId::T6);
REQUIRE(zone->m_platform == GamePlatform::PC);
REQUIRE(zone->m_name == "CombinedZoneT6");
REQUIRE(pools->GetTotalAssetCount() == 2);
REQUIRE(pools->m_technique_set->GetAsset("trivial_floatz_2992w610"));
const auto* material = pools->m_material->GetAsset("test");
REQUIRE(material);
REQUIRE(material->Asset()->techniqueSet);
REQUIRE(material->Asset()->techniqueSet->name == "trivial_floatz_2992w610"s);
REQUIRE(material->Asset()->techniqueSet->techniques[T6::TECHNIQUE_UNLIT]);
}
// x64 for now produces invalid zones, don't try to load them yet
#ifdef ARCH_x86
TEST_CASE("Extend and dereference(T6)", "[t6][system][simple]")
{
const auto testDir = oat::paths::GetSystemTestsDirectory() / "Game/T6/ExtendAndDereference";
const auto outputPath = oat::paths::GetTempDirectory("ExtendAndDereferenceT6");
BuildZoneWithMaterial(testDir, outputPath);
BuildCombinedZone(testDir, outputPath);
CheckCombinedZoneContent(outputPath);
}
#endif
} // namespace

View File

@@ -0,0 +1,6 @@
>game,T6
material,,Suzanne
material,,Suzanne2
xmodel,Suzanne1
xmodel,Suzanne2

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,20 @@
{
"$schema": "http://openassettools.dev/schema/xmodel.v1.json",
"_game": "t6",
"_type": "xmodel",
"_version": 2,
"flags": 2621440,
"lightingOriginOffset": {
"x": 0.0,
"y": 0.0,
"z": 0.5
},
"lightingOriginRange": 0.5,
"lods": [
{
"distance": 11447.6904296875,
"file": "model_export/Suzanne1.gltf"
}
],
"type": "rigid"
}

View File

@@ -0,0 +1,20 @@
{
"$schema": "http://openassettools.dev/schema/xmodel.v1.json",
"_game": "t6",
"_type": "xmodel",
"_version": 2,
"flags": 2621440,
"lightingOriginOffset": {
"x": 0.0,
"y": 0.0,
"z": 0.5
},
"lightingOriginRange": 0.5,
"lods": [
{
"distance": 11447.6904296875,
"file": "model_export/Suzanne2.gltf"
}
],
"type": "rigid"
}

View File

@@ -0,0 +1,4 @@
>game,T6
ignore,Ignored
xmodel,Suzanne2

View File

@@ -0,0 +1,77 @@
{
"$schema": "http://openassettools.dev/schema/material.v1.json",
"_game": "t6",
"_type": "material",
"_version": 1,
"cameraRegion": "none",
"constants": [],
"contents": 1,
"gameFlags": [],
"layeredSurfaceTypes": 536870912,
"sortKey": 4,
"stateBits": [
{
"alphaTest": "disabled",
"blendOpAlpha": "disabled",
"blendOpRgb": "disabled",
"colorWriteAlpha": true,
"colorWriteRgb": true,
"cullFace": "back",
"depthTest": "disabled",
"depthWrite": false,
"dstBlendAlpha": "zero",
"dstBlendRgb": "zero",
"polygonOffset": "offset0",
"polymodeLine": false,
"srcBlendAlpha": "one",
"srcBlendRgb": "one"
}
],
"stateBitsEntry": [
-1,
-1,
0,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1
],
"stateFlags": 0,
"surfaceFlags": 0,
"surfaceTypeBits": 0,
"techniqueSet": "trivial_floatz_2992w610",
"textureAtlas": {
"columns": 1,
"rows": 1
},
"textures": []
}

View File

@@ -0,0 +1,77 @@
{
"$schema": "http://openassettools.dev/schema/material.v1.json",
"_game": "t6",
"_type": "material",
"_version": 1,
"cameraRegion": "none",
"constants": [],
"contents": 1,
"gameFlags": [],
"layeredSurfaceTypes": 536870912,
"sortKey": 4,
"stateBits": [
{
"alphaTest": "disabled",
"blendOpAlpha": "disabled",
"blendOpRgb": "disabled",
"colorWriteAlpha": true,
"colorWriteRgb": true,
"cullFace": "back",
"depthTest": "disabled",
"depthWrite": false,
"dstBlendAlpha": "zero",
"dstBlendRgb": "zero",
"polygonOffset": "offset0",
"polymodeLine": false,
"srcBlendAlpha": "one",
"srcBlendRgb": "one"
}
],
"stateBitsEntry": [
-1,
-1,
0,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1
],
"stateFlags": 0,
"surfaceFlags": 0,
"surfaceTypeBits": 0,
"techniqueSet": "trivial_floatz_2992w610",
"textureAtlas": {
"columns": 1,
"rows": 1
},
"textures": []
}

View File

@@ -0,0 +1 @@
techniqueset,trivial_floatz_2992w610
1 techniqueset trivial_floatz_2992w610

View File

@@ -0,0 +1,137 @@
/*
This makes sure that when reusing assets from an existing zone, asset dependencies that are references can be overwritten to be non-references.
In this case:
- The zone `ZoneWithTechsetT6` contains the techniqueset `trivial_floatz_2992w610`
- The zone `ZoneWithMaterialT6` contains the material `test` and with a reference to its techniqueset `,trivial_floatz_2992w610`
- The final zone `CombinedZoneT6` is built while loading both of the other fastfile and is expected to contain both the material `test` with a reference to a
(actual asset, not a reference) techniqueset `trivial_floatz_2992w610`
*/
#include "Game/T6/GameAssetPoolT6.h"
#include "Linker.h"
#include "OatTestPaths.h"
#include "SystemTestsPaths.h"
#include "Unlinker.h"
#include "ZoneLoading.h"
#include <catch2/catch_test_macros.hpp>
#include <filesystem>
#include <format>
#include <memory>
#include <string>
namespace fs = std::filesystem;
using namespace std::literals;
namespace
{
void BuildBaseZone(const fs::path& testDir, const fs::path& outputPath)
{
const auto testDirStr = testDir.string();
const auto assetDirStr = (testDir / "BaseZone").string();
const auto outputPathStr = outputPath.string();
const char* argStrings[]{
"SystemTests", // bin
"--verbose",
"--add-asset-search-path",
assetDirStr.c_str(),
"--source-search-path",
testDirStr.c_str(),
"--output-folder",
outputPathStr.c_str(),
"BaseZone",
};
LinkerArgs args;
bool shouldContinue = true;
const auto couldParseArgs = args.ParseArgs(std::extent_v<decltype(argStrings)>, argStrings, shouldContinue);
REQUIRE(couldParseArgs);
REQUIRE(shouldContinue);
const auto linker = Linker::Create(std::move(args));
const auto linkerResult = linker->Start();
REQUIRE(linkerResult);
}
void BuildTestZone(const fs::path& testDir, const fs::path& outputPath)
{
const auto testDirStr = testDir.string();
const auto assetDirStr = (testDir / "TestZone").string();
const auto outputPathStr = outputPath.string();
const auto baseZonePathStr = (outputPath / "BaseZone.ff").string();
const char* argStrings[]{
"SystemTests", // bin
"--verbose",
"--load",
baseZonePathStr.c_str(),
"--add-asset-search-path",
assetDirStr.c_str(),
"--source-search-path",
testDirStr.c_str(),
"--output-folder",
outputPathStr.c_str(),
"TestZone",
"TestZone", // build twice to ensure the second build uses a global asset that the first build already used
};
LinkerArgs args;
bool shouldContinue = true;
const auto couldParseArgs = args.ParseArgs(std::extent_v<decltype(argStrings)>, argStrings, shouldContinue);
REQUIRE(couldParseArgs);
REQUIRE(shouldContinue);
const auto linker = Linker::Create(std::move(args));
const auto linkerResult = linker->Start();
REQUIRE(linkerResult);
}
void CheckTestZoneContent(const fs::path& outputPath)
{
const auto expectedZonePathStr = (outputPath / "TestZone.ff").string();
auto maybeZone = ZoneLoading::LoadZone(expectedZonePathStr, std::nullopt);
REQUIRE(maybeZone);
auto zone = std::move(*maybeZone);
auto pools = dynamic_cast<GameAssetPoolT6*>(zone->m_pools.get());
REQUIRE(zone->m_game_id == GameId::T6);
REQUIRE(zone->m_platform == GamePlatform::PC);
REQUIRE(zone->m_name == "TestZone");
REQUIRE(pools->GetTotalAssetCount() == 3);
REQUIRE(pools->m_material->GetAsset("Suzanne2"));
REQUIRE(pools->m_technique_set->GetAsset(",trivial_floatz_2992w610"));
const auto* xmodel = pools->m_xmodel->GetAsset("Suzanne2");
REQUIRE(xmodel);
REQUIRE(xmodel->Asset()->boneNames);
REQUIRE(xmodel->Asset()->numRootBones == 1);
REQUIRE(xmodel->Asset()->numBones == 3);
REQUIRE(zone->m_script_strings.Value(xmodel->Asset()->boneNames[0]) == "Root2");
REQUIRE(zone->m_script_strings.Value(xmodel->Asset()->boneNames[1]) == "EarLeft2");
REQUIRE(zone->m_script_strings.Value(xmodel->Asset()->boneNames[2]) == "EarRight2");
}
// x64 for now produces invalid zones, don't try to load them yet
#ifdef ARCH_x86
TEST_CASE("Reuse assets from global asset pool(T6)", "[t6][system][simple]")
{
const auto testDir = oat::paths::GetSystemTestsDirectory() / "Game/T6/ReuseGlobalAssetPoolsAssets";
const auto outputPath = oat::paths::GetTempDirectory("ReuseGlobalAssetPoolsAssetsT6");
BuildBaseZone(testDir, outputPath);
BuildTestZone(testDir, outputPath);
CheckTestZoneContent(outputPath);
}
#endif
} // namespace

View File

@@ -0,0 +1 @@
This is a simple zone.

View File

@@ -0,0 +1,4 @@
>game,T6
rawfile,SimpleZone.txt

View File

@@ -0,0 +1,65 @@
#include "Game/T6/GameAssetPoolT6.h"
#include "Linker.h"
#include "OatTestPaths.h"
#include "SystemTestsPaths.h"
#include "ZoneLoading.h"
#include <catch2/catch_test_macros.hpp>
#include <filesystem>
#include <format>
#include <memory>
#include <string>
namespace fs = std::filesystem;
using namespace std::literals;
namespace
{
TEST_CASE("Simple Zone(T6)", "[t6][system][simple]")
{
const auto assetSearchPath = (oat::paths::GetSystemTestsDirectory() / "Game/T6/Simple").string();
const auto sourceSearchPath = (oat::paths::GetSystemTestsDirectory() / "Game/T6/Simple").string();
const auto outputPath = oat::paths::GetTempDirectory("SimpleZoneT6").string();
const char* argStrings[]{
"SystemTests", // bin
"--verbose",
"--asset-search-path",
assetSearchPath.c_str(),
"--source-search-path",
sourceSearchPath.c_str(),
"--output-folder",
outputPath.c_str(),
"SimpleZoneT6",
};
LinkerArgs args;
bool shouldContinue = true;
const auto couldParseArgs = args.ParseArgs(std::extent_v<decltype(argStrings)>, argStrings, shouldContinue);
REQUIRE(couldParseArgs);
REQUIRE(shouldContinue);
const auto linker = Linker::Create(std::move(args));
const auto linkerResult = linker->Start();
REQUIRE(linkerResult);
// x64 for now produces invalid zones, don't try to load them yet
#ifdef ARCH_x86
const auto expectedZonePath = (fs::path(outputPath) / "SimpleZoneT6.ff").string();
auto maybeZone = ZoneLoading::LoadZone(expectedZonePath, std::nullopt);
REQUIRE(maybeZone);
auto zone = std::move(*maybeZone);
auto pools = dynamic_cast<GameAssetPoolT6*>(zone->m_pools.get());
REQUIRE(zone->m_game_id == GameId::T6);
REQUIRE(zone->m_platform == GamePlatform::PC);
REQUIRE(zone->m_name == "SimpleZoneT6");
REQUIRE(pools->GetTotalAssetCount() == 1);
REQUIRE(pools->m_raw_file->GetAsset("SimpleZone.txt"));
#endif
}
} // namespace

View File

@@ -0,0 +1,15 @@
#include "SystemTestsPaths.h"
#include "OatTestPaths.h"
#include <filesystem>
namespace fs = std::filesystem;
namespace oat::paths
{
std::filesystem::path GetSystemTestsDirectory()
{
return GetTestDirectory() / "SystemTests";
}
} // namespace oat::paths

Some files were not shown because too many files have changed in this diff Show More