mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2026-01-24 08:53:04 +00:00
chore: restructure Linker and Unlinker for system testing
This commit is contained in:
24
src/Unlinking/ContentLister/ContentPrinter.cpp
Normal file
24
src/Unlinking/ContentLister/ContentPrinter.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include "ContentPrinter.h"
|
||||
|
||||
#include "Utils/Logging/Log.h"
|
||||
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
|
||||
ContentPrinter::ContentPrinter(const Zone& zone)
|
||||
: m_zone(zone)
|
||||
{
|
||||
}
|
||||
|
||||
void ContentPrinter::PrintContent() const
|
||||
{
|
||||
const auto* pools = m_zone.m_pools.get();
|
||||
const auto* game = IGame::GetGameById(m_zone.m_game_id);
|
||||
con::info("Zone '{}' ({})", m_zone.m_name, game->GetShortName());
|
||||
con::info("Content:");
|
||||
|
||||
for (const auto& asset : *pools)
|
||||
con::info("{}, {}", *pools->GetAssetTypeName(asset->m_type), asset->m_name);
|
||||
|
||||
con::info("");
|
||||
}
|
||||
14
src/Unlinking/ContentLister/ContentPrinter.h
Normal file
14
src/Unlinking/ContentLister/ContentPrinter.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "Zone/Zone.h"
|
||||
|
||||
class ContentPrinter
|
||||
{
|
||||
public:
|
||||
explicit ContentPrinter(const Zone& zone);
|
||||
|
||||
void PrintContent() const;
|
||||
|
||||
private:
|
||||
const Zone& m_zone;
|
||||
};
|
||||
327
src/Unlinking/Unlinker.cpp
Normal file
327
src/Unlinking/Unlinker.cpp
Normal file
@@ -0,0 +1,327 @@
|
||||
#include "Unlinker.h"
|
||||
|
||||
#include "ContentLister/ContentPrinter.h"
|
||||
#include "IObjLoader.h"
|
||||
#include "IObjWriter.h"
|
||||
#include "ObjWriting.h"
|
||||
#include "SearchPath/IWD.h"
|
||||
#include "SearchPath/OutputPathFilesystem.h"
|
||||
#include "UnlinkerArgs.h"
|
||||
#include "UnlinkerPaths.h"
|
||||
#include "Utils/ClassUtils.h"
|
||||
#include "Utils/Logging/Log.h"
|
||||
#include "Utils/ObjFileStream.h"
|
||||
#include "Zone/Definition/ZoneDefWriter.h"
|
||||
#include "ZoneLoading.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace
|
||||
{
|
||||
class UnlinkerImpl : public Unlinker
|
||||
{
|
||||
public:
|
||||
UnlinkerImpl(UnlinkerArgs args)
|
||||
: m_args(std::move(args))
|
||||
{
|
||||
}
|
||||
|
||||
bool Start() override
|
||||
{
|
||||
UnlinkerPaths paths;
|
||||
if (!paths.LoadUserPaths(m_args))
|
||||
return false;
|
||||
|
||||
if (!LoadZones(paths))
|
||||
return false;
|
||||
|
||||
const auto result = UnlinkZones(paths);
|
||||
|
||||
UnloadZones();
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] bool ShouldLoadObj() const
|
||||
{
|
||||
return m_args.m_task != UnlinkerArgs::ProcessingTask::LIST && !m_args.m_skip_obj;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool WriteZoneDefinitionFile(const Zone& zone, const fs::path& zoneDefinitionFileFolder) const
|
||||
{
|
||||
auto zoneDefinitionFilePath(zoneDefinitionFileFolder);
|
||||
zoneDefinitionFilePath.append(zone.m_name);
|
||||
zoneDefinitionFilePath.replace_extension(".zone");
|
||||
|
||||
std::ofstream zoneDefinitionFile(zoneDefinitionFilePath, std::fstream::out | std::fstream::binary);
|
||||
if (!zoneDefinitionFile.is_open())
|
||||
{
|
||||
con::error("Failed to open file for zone definition file of zone \"{}\".", zone.m_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto* zoneDefWriter = IZoneDefWriter::GetZoneDefWriterForGame(zone.m_game_id);
|
||||
zoneDefWriter->WriteZoneDef(zoneDefinitionFile, zone, m_args.m_use_gdt);
|
||||
|
||||
zoneDefinitionFile.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool OpenGdtFile(const Zone& zone, const fs::path& outputFolder, std::ofstream& stream)
|
||||
{
|
||||
auto gdtFilePath(outputFolder);
|
||||
gdtFilePath.append("source_data");
|
||||
|
||||
fs::create_directories(gdtFilePath);
|
||||
|
||||
gdtFilePath.append(zone.m_name);
|
||||
gdtFilePath.replace_extension(".gdt");
|
||||
|
||||
stream = std::ofstream(gdtFilePath, std::fstream::out | std::fstream::binary);
|
||||
if (!stream.is_open())
|
||||
{
|
||||
con::error("Failed to open file for zone definition file of zone \"{}\".", zone.m_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UpdateAssetIncludesAndExcludes(const AssetDumpingContext& context) const
|
||||
{
|
||||
const auto assetTypeCount = context.m_zone.m_pools->GetAssetTypeCount();
|
||||
|
||||
ObjWriting::Configuration.AssetTypesToHandleBitfield = std::vector<bool>(assetTypeCount);
|
||||
|
||||
std::vector<bool> handledSpecifiedAssets(m_args.m_specified_asset_types.size());
|
||||
for (auto i = 0u; i < assetTypeCount; i++)
|
||||
{
|
||||
const auto assetTypeName = std::string(*context.m_zone.m_pools->GetAssetTypeName(i));
|
||||
|
||||
const auto foundSpecifiedEntry = m_args.m_specified_asset_type_map.find(assetTypeName);
|
||||
if (foundSpecifiedEntry != m_args.m_specified_asset_type_map.end())
|
||||
{
|
||||
ObjWriting::Configuration.AssetTypesToHandleBitfield[i] = m_args.m_asset_type_handling == UnlinkerArgs::AssetTypeHandling::INCLUDE;
|
||||
assert(foundSpecifiedEntry->second < handledSpecifiedAssets.size());
|
||||
handledSpecifiedAssets[foundSpecifiedEntry->second] = true;
|
||||
}
|
||||
else
|
||||
ObjWriting::Configuration.AssetTypesToHandleBitfield[i] = m_args.m_asset_type_handling == UnlinkerArgs::AssetTypeHandling::EXCLUDE;
|
||||
}
|
||||
|
||||
auto anySpecifiedValueInvalid = false;
|
||||
for (auto i = 0u; i < handledSpecifiedAssets.size(); i++)
|
||||
{
|
||||
if (!handledSpecifiedAssets[i])
|
||||
{
|
||||
con::error("Unknown asset type \"{}\"", m_args.m_specified_asset_types[i]);
|
||||
anySpecifiedValueInvalid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anySpecifiedValueInvalid)
|
||||
{
|
||||
con::error("Valid asset types are:");
|
||||
|
||||
auto first = true;
|
||||
std::ostringstream ss;
|
||||
for (auto i = 0u; i < assetTypeCount; i++)
|
||||
{
|
||||
const auto assetTypeName = std::string(*context.m_zone.m_pools->GetAssetTypeName(i));
|
||||
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
ss << ", ";
|
||||
ss << assetTypeName;
|
||||
}
|
||||
con::error(ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Performs the tasks specified by the command line arguments on the specified zone.
|
||||
* \param searchPath The search path for obj data.
|
||||
* \param zone The zone to handle.
|
||||
* \return \c true if handling the zone was successful, otherwise \c false
|
||||
*/
|
||||
bool HandleZone(ISearchPath& searchPath, Zone& zone) const
|
||||
{
|
||||
if (m_args.m_task == UnlinkerArgs::ProcessingTask::LIST)
|
||||
{
|
||||
const ContentPrinter printer(zone);
|
||||
printer.PrintContent();
|
||||
}
|
||||
else if (m_args.m_task == UnlinkerArgs::ProcessingTask::DUMP)
|
||||
{
|
||||
const auto outputFolderPathStr = m_args.GetOutputFolderPathForZone(zone);
|
||||
const fs::path outputFolderPath(outputFolderPathStr);
|
||||
fs::create_directories(outputFolderPath);
|
||||
|
||||
fs::path zoneDefinitionFileFolder(outputFolderPath);
|
||||
zoneDefinitionFileFolder.append("zone_source");
|
||||
fs::create_directories(zoneDefinitionFileFolder);
|
||||
|
||||
if (!WriteZoneDefinitionFile(zone, zoneDefinitionFileFolder))
|
||||
return false;
|
||||
|
||||
OutputPathFilesystem outputFolderOutputPath(outputFolderPath);
|
||||
AssetDumpingContext context(zone, outputFolderPathStr, outputFolderOutputPath, searchPath, std::nullopt);
|
||||
|
||||
std::ofstream gdtStream;
|
||||
if (m_args.m_use_gdt)
|
||||
{
|
||||
if (!OpenGdtFile(zone, outputFolderPath, gdtStream))
|
||||
return false;
|
||||
auto gdt = std::make_unique<GdtOutputStream>(gdtStream);
|
||||
gdt->BeginStream();
|
||||
|
||||
const auto* game = IGame::GetGameById(zone.m_game_id);
|
||||
gdt->WriteVersion(GdtVersion(game->GetShortName(), 1));
|
||||
|
||||
context.m_gdt = std::move(gdt);
|
||||
}
|
||||
|
||||
UpdateAssetIncludesAndExcludes(context);
|
||||
|
||||
const auto* objWriter = IObjWriter::GetObjWriterForGame(zone.m_game_id);
|
||||
|
||||
auto result = objWriter->DumpZone(context);
|
||||
|
||||
if (m_args.m_use_gdt)
|
||||
{
|
||||
context.m_gdt->EndStream();
|
||||
gdtStream.close();
|
||||
}
|
||||
|
||||
if (!result)
|
||||
{
|
||||
con::error("Dumping zone failed!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadZones(UnlinkerPaths& paths)
|
||||
{
|
||||
for (const auto& zonePath : m_args.m_zones_to_load)
|
||||
{
|
||||
if (!fs::is_regular_file(zonePath))
|
||||
{
|
||||
con::error("Could not find file \"{}\".", zonePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto absoluteZoneDirectory = absolute(std::filesystem::path(zonePath).remove_filename()).string();
|
||||
|
||||
auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory);
|
||||
auto maybeZone = ZoneLoading::LoadZone(zonePath, std::nullopt);
|
||||
if (!maybeZone)
|
||||
{
|
||||
con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error());
|
||||
return false;
|
||||
}
|
||||
|
||||
auto zone = std::move(*maybeZone);
|
||||
|
||||
con::debug("Loaded zone \"{}\"", zone->m_name);
|
||||
|
||||
if (ShouldLoadObj())
|
||||
{
|
||||
const auto* objLoader = IObjLoader::GetObjLoaderForGame(zone->m_game_id);
|
||||
objLoader->LoadReferencedContainersForZone(*searchPathsForZone, *zone);
|
||||
}
|
||||
|
||||
m_loaded_zones.emplace_back(std::move(zone));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UnloadZones()
|
||||
{
|
||||
for (auto i = m_loaded_zones.rbegin(); i != m_loaded_zones.rend(); ++i)
|
||||
{
|
||||
auto& loadedZone = *i;
|
||||
|
||||
// Copy zone name since we deallocate before logging
|
||||
const auto zoneName = loadedZone->m_name;
|
||||
|
||||
if (ShouldLoadObj())
|
||||
{
|
||||
const auto* objLoader = IObjLoader::GetObjLoaderForGame(loadedZone->m_game_id);
|
||||
objLoader->UnloadContainersOfZone(*loadedZone);
|
||||
}
|
||||
|
||||
loadedZone.reset();
|
||||
|
||||
con::debug("Unloaded zone \"{}\"", zoneName);
|
||||
}
|
||||
m_loaded_zones.clear();
|
||||
}
|
||||
|
||||
bool UnlinkZones(UnlinkerPaths& paths) const
|
||||
{
|
||||
for (const auto& zonePath : m_args.m_zones_to_unlink)
|
||||
{
|
||||
if (!fs::is_regular_file(zonePath))
|
||||
{
|
||||
con::error("Could not find file \"{}\".", zonePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto zoneDirectory = fs::path(zonePath).remove_filename();
|
||||
if (zoneDirectory.empty())
|
||||
zoneDirectory = fs::current_path();
|
||||
auto absoluteZoneDirectory = absolute(zoneDirectory).string();
|
||||
|
||||
auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory);
|
||||
|
||||
auto maybeZone = ZoneLoading::LoadZone(zonePath, std::nullopt);
|
||||
if (!maybeZone)
|
||||
{
|
||||
con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error());
|
||||
return false;
|
||||
}
|
||||
|
||||
auto zone = std::move(*maybeZone);
|
||||
|
||||
con::debug("Loaded zone \"{}\"", zone->m_name);
|
||||
|
||||
const auto* objLoader = IObjLoader::GetObjLoaderForGame(zone->m_game_id);
|
||||
if (ShouldLoadObj())
|
||||
objLoader->LoadReferencedContainersForZone(*searchPathsForZone, *zone);
|
||||
|
||||
if (!HandleZone(*searchPathsForZone, *zone))
|
||||
return false;
|
||||
|
||||
if (ShouldLoadObj())
|
||||
objLoader->UnloadContainersOfZone(*zone);
|
||||
|
||||
// Copy zone name for using it after freeing the zone
|
||||
std::string zoneName = zone->m_name;
|
||||
zone.reset();
|
||||
con::debug("Unloaded zone \"{}\"", zoneName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
UnlinkerArgs m_args;
|
||||
std::vector<std::unique_ptr<Zone>> m_loaded_zones;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<Unlinker> Unlinker::Create(UnlinkerArgs args)
|
||||
{
|
||||
return std::make_unique<UnlinkerImpl>(std::move(args));
|
||||
}
|
||||
19
src/Unlinking/Unlinker.h
Normal file
19
src/Unlinking/Unlinker.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "UnlinkerArgs.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class Unlinker
|
||||
{
|
||||
public:
|
||||
virtual ~Unlinker() = default;
|
||||
|
||||
static std::unique_ptr<Unlinker> Create(UnlinkerArgs args);
|
||||
|
||||
/**
|
||||
* \brief Starts the Unlinker application logic.
|
||||
* \return \c true if the application was successful or \c false if an error occurred.
|
||||
*/
|
||||
virtual bool Start() = 0;
|
||||
};
|
||||
394
src/Unlinking/UnlinkerArgs.cpp
Normal file
394
src/Unlinking/UnlinkerArgs.cpp
Normal file
@@ -0,0 +1,394 @@
|
||||
#include "UnlinkerArgs.h"
|
||||
|
||||
#include "GitVersion.h"
|
||||
#include "ObjLoading.h"
|
||||
#include "ObjWriting.h"
|
||||
#include "Utils/Arguments/UsageInformation.h"
|
||||
#include "Utils/FileUtils.h"
|
||||
#include "Utils/Logging/Log.h"
|
||||
#include "Utils/StringUtils.h"
|
||||
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <regex>
|
||||
#include <type_traits>
|
||||
|
||||
// clang-format off
|
||||
const CommandLineOption* const OPTION_HELP =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithShortName("?")
|
||||
.WithLongName("help")
|
||||
.WithDescription("Displays usage information.")
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_VERSION =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithLongName("version")
|
||||
.WithDescription("Prints the application version.")
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_VERBOSE =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithShortName("v")
|
||||
.WithLongName("verbose")
|
||||
.WithDescription("Outputs a lot more and more detailed messages.")
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_NO_COLOR =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithLongName("no-color")
|
||||
.WithDescription("Disables colored terminal output.")
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_MINIMAL_ZONE_FILE =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithShortName("min")
|
||||
.WithLongName("minimal-zone")
|
||||
.WithDescription("Minimizes the size of the zone file output by only including assets that are not a dependency of another asset.")
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_LOAD =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithShortName("l")
|
||||
.WithLongName("load")
|
||||
.WithDescription("Loads an existing zone before trying to unlink any zone.")
|
||||
.WithParameter("zonePath")
|
||||
.Reusable()
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_LIST =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithLongName("list")
|
||||
.WithDescription("Lists the contents of a zone instead of writing them to the disk.")
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_OUTPUT_FOLDER =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithShortName("o")
|
||||
.WithLongName("output-folder")
|
||||
.WithDescription(std::format("Specifies the output folder containing the contents of the unlinked zones. Defaults to \"{}\"", UnlinkerArgs::DEFAULT_OUTPUT_FOLDER))
|
||||
.WithParameter("outputFolderPath")
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_SEARCH_PATH =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithLongName("search-path")
|
||||
.WithDescription("Specifies a semi-colon separated list of paths to search for additional game files.")
|
||||
.WithParameter("searchPathString")
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_IMAGE_FORMAT =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithLongName("image-format")
|
||||
.WithDescription("Specifies the format of dumped image files. Valid values are: DDS, IWI")
|
||||
.WithParameter("imageFormatValue")
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_MODEL_FORMAT =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithLongName("model-format")
|
||||
.WithDescription("Specifies the format of dumped model files. Valid values are: XMODEL_EXPORT, XMODEL_BIN, OBJ, GLTF, GLB")
|
||||
.WithParameter("modelFormatValue")
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_SKIP_OBJ =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithLongName("skip-obj")
|
||||
.WithDescription("Skips loading raw obj data.")
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_GDT =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithLongName("gdt")
|
||||
.WithDescription("Dumps assets in a GDT whenever possible.")
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_EXCLUDE_ASSETS =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithLongName("exclude-assets")
|
||||
.WithDescription("Specify all asset types that should be excluded.")
|
||||
.WithParameter("assetTypeList")
|
||||
.Reusable()
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_INCLUDE_ASSETS =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithLongName("include-assets")
|
||||
.WithDescription("Specify all asset types that should be included.")
|
||||
.WithParameter("assetTypeList")
|
||||
.Reusable()
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_LEGACY_MENUS =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithLongName("legacy-menus")
|
||||
.WithDescription("Dumps menus with a compatibility mode to work with applications not compatible with the newer dumping mode.")
|
||||
.Build();
|
||||
|
||||
// clang-format on
|
||||
|
||||
const CommandLineOption* const COMMAND_LINE_OPTIONS[]{
|
||||
OPTION_HELP,
|
||||
OPTION_VERSION,
|
||||
OPTION_VERBOSE,
|
||||
OPTION_NO_COLOR,
|
||||
OPTION_MINIMAL_ZONE_FILE,
|
||||
OPTION_LOAD,
|
||||
OPTION_LIST,
|
||||
OPTION_OUTPUT_FOLDER,
|
||||
OPTION_SEARCH_PATH,
|
||||
OPTION_IMAGE_FORMAT,
|
||||
OPTION_MODEL_FORMAT,
|
||||
OPTION_SKIP_OBJ,
|
||||
OPTION_GDT,
|
||||
OPTION_EXCLUDE_ASSETS,
|
||||
OPTION_INCLUDE_ASSETS,
|
||||
OPTION_LEGACY_MENUS,
|
||||
};
|
||||
|
||||
UnlinkerArgs::UnlinkerArgs()
|
||||
: m_argument_parser(COMMAND_LINE_OPTIONS, std::extent_v<decltype(COMMAND_LINE_OPTIONS)>),
|
||||
m_zone_pattern(R"(\?zone\?)"),
|
||||
m_task(ProcessingTask::DUMP),
|
||||
m_minimal_zone_def(false),
|
||||
m_asset_type_handling(AssetTypeHandling::EXCLUDE),
|
||||
m_skip_obj(false),
|
||||
m_use_gdt(false)
|
||||
{
|
||||
}
|
||||
|
||||
void UnlinkerArgs::PrintUsage() const
|
||||
{
|
||||
UsageInformation usage(m_argument_parser.GetExecutableName());
|
||||
|
||||
for (const auto* commandLineOption : COMMAND_LINE_OPTIONS)
|
||||
{
|
||||
usage.AddCommandLineOption(commandLineOption);
|
||||
}
|
||||
|
||||
usage.AddArgument("pathToZone");
|
||||
usage.SetVariableArguments(true);
|
||||
|
||||
usage.Print();
|
||||
}
|
||||
|
||||
void UnlinkerArgs::PrintVersion()
|
||||
{
|
||||
con::info("OpenAssetTools Unlinker {}", GIT_VERSION);
|
||||
}
|
||||
|
||||
bool UnlinkerArgs::SetImageDumpingMode() const
|
||||
{
|
||||
auto specifiedValue = m_argument_parser.GetValueForOption(OPTION_IMAGE_FORMAT);
|
||||
utils::MakeStringLowerCase(specifiedValue);
|
||||
|
||||
if (specifiedValue == "dds")
|
||||
{
|
||||
ObjWriting::Configuration.ImageOutputFormat = ObjWriting::Configuration_t::ImageOutputFormat_e::DDS;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (specifiedValue == "iwi")
|
||||
{
|
||||
ObjWriting::Configuration.ImageOutputFormat = ObjWriting::Configuration_t::ImageOutputFormat_e::IWI;
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string originalValue = m_argument_parser.GetValueForOption(OPTION_IMAGE_FORMAT);
|
||||
con::error("Illegal value: \"{}\" is not a valid image output format. Use -? to see usage information.", originalValue);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnlinkerArgs::SetModelDumpingMode() const
|
||||
{
|
||||
auto specifiedValue = m_argument_parser.GetValueForOption(OPTION_MODEL_FORMAT);
|
||||
utils::MakeStringLowerCase(specifiedValue);
|
||||
|
||||
if (specifiedValue == "xmodel_export")
|
||||
{
|
||||
ObjWriting::Configuration.ModelOutputFormat = ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (specifiedValue == "xmodel_bin")
|
||||
{
|
||||
ObjWriting::Configuration.ModelOutputFormat = ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_BIN;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (specifiedValue == "obj")
|
||||
{
|
||||
ObjWriting::Configuration.ModelOutputFormat = ObjWriting::Configuration_t::ModelOutputFormat_e::OBJ;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (specifiedValue == "gltf")
|
||||
{
|
||||
ObjWriting::Configuration.ModelOutputFormat = ObjWriting::Configuration_t::ModelOutputFormat_e::GLTF;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (specifiedValue == "glb")
|
||||
{
|
||||
ObjWriting::Configuration.ModelOutputFormat = ObjWriting::Configuration_t::ModelOutputFormat_e::GLB;
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string originalValue = m_argument_parser.GetValueForOption(OPTION_MODEL_FORMAT);
|
||||
con::error("Illegal value: \"{}\" is not a valid model output format. Use -? to see usage information.", originalValue);
|
||||
return false;
|
||||
}
|
||||
|
||||
void UnlinkerArgs::AddSpecifiedAssetType(std::string value)
|
||||
{
|
||||
const auto alreadySpecifiedAssetType = m_specified_asset_type_map.find(value);
|
||||
if (alreadySpecifiedAssetType == m_specified_asset_type_map.end())
|
||||
{
|
||||
m_specified_asset_type_map.emplace(std::make_pair(value, m_specified_asset_types.size()));
|
||||
m_specified_asset_types.emplace_back(std::move(value));
|
||||
}
|
||||
}
|
||||
|
||||
void UnlinkerArgs::ParseCommaSeparatedAssetTypeString(const std::string& input)
|
||||
{
|
||||
auto currentPos = 0uz;
|
||||
size_t endPos;
|
||||
|
||||
std::string lowerInput(input);
|
||||
utils::MakeStringLowerCase(lowerInput);
|
||||
|
||||
while (currentPos < lowerInput.size() && (endPos = lowerInput.find_first_of(',', currentPos)) != std::string::npos)
|
||||
{
|
||||
AddSpecifiedAssetType(std::string(lowerInput, currentPos, endPos - currentPos));
|
||||
currentPos = endPos + 1;
|
||||
}
|
||||
|
||||
if (currentPos < lowerInput.size())
|
||||
AddSpecifiedAssetType(std::string(lowerInput, currentPos, lowerInput.size() - currentPos));
|
||||
}
|
||||
|
||||
bool UnlinkerArgs::ParseArgs(const int argc, const char** argv, bool& shouldContinue)
|
||||
{
|
||||
shouldContinue = true;
|
||||
if (!m_argument_parser.ParseArguments(argc, argv))
|
||||
{
|
||||
PrintUsage();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the user requested help
|
||||
if (m_argument_parser.IsOptionSpecified(OPTION_HELP))
|
||||
{
|
||||
PrintUsage();
|
||||
shouldContinue = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the user wants to see the version
|
||||
if (m_argument_parser.IsOptionSpecified(OPTION_VERSION))
|
||||
{
|
||||
PrintVersion();
|
||||
shouldContinue = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
m_zones_to_unlink = m_argument_parser.GetArguments();
|
||||
const size_t zoneCount = m_zones_to_unlink.size();
|
||||
if (zoneCount < 1)
|
||||
{
|
||||
// No zones to load specified...
|
||||
PrintUsage();
|
||||
return false;
|
||||
}
|
||||
|
||||
// -v; --verbose
|
||||
if (m_argument_parser.IsOptionSpecified(OPTION_VERBOSE))
|
||||
con::set_log_level(con::LogLevel::DEBUG);
|
||||
else
|
||||
con::set_log_level(con::LogLevel::INFO);
|
||||
|
||||
// --no-color
|
||||
con::set_use_color(!m_argument_parser.IsOptionSpecified(OPTION_NO_COLOR));
|
||||
|
||||
// -min; --minimal-zone
|
||||
m_minimal_zone_def = m_argument_parser.IsOptionSpecified(OPTION_MINIMAL_ZONE_FILE);
|
||||
|
||||
// -l; --load
|
||||
if (m_argument_parser.IsOptionSpecified(OPTION_LOAD))
|
||||
m_zones_to_load = m_argument_parser.GetParametersForOption(OPTION_LOAD);
|
||||
|
||||
// --list
|
||||
if (m_argument_parser.IsOptionSpecified(OPTION_LIST))
|
||||
m_task = ProcessingTask::LIST;
|
||||
|
||||
// -o; --output-folder
|
||||
if (m_argument_parser.IsOptionSpecified(OPTION_OUTPUT_FOLDER))
|
||||
m_output_folder = m_argument_parser.GetValueForOption(OPTION_OUTPUT_FOLDER);
|
||||
else
|
||||
m_output_folder = DEFAULT_OUTPUT_FOLDER;
|
||||
|
||||
// --search-path
|
||||
if (m_argument_parser.IsOptionSpecified(OPTION_SEARCH_PATH))
|
||||
{
|
||||
if (!FileUtils::ParsePathsString(m_argument_parser.GetValueForOption(OPTION_SEARCH_PATH), m_user_search_paths))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// --image-format
|
||||
if (m_argument_parser.IsOptionSpecified(OPTION_IMAGE_FORMAT))
|
||||
{
|
||||
if (!SetImageDumpingMode())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// --model-format
|
||||
if (m_argument_parser.IsOptionSpecified(OPTION_MODEL_FORMAT))
|
||||
{
|
||||
if (!SetModelDumpingMode())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// --skip-obj
|
||||
m_skip_obj = m_argument_parser.IsOptionSpecified(OPTION_SKIP_OBJ);
|
||||
|
||||
// --gdt
|
||||
m_use_gdt = m_argument_parser.IsOptionSpecified(OPTION_GDT);
|
||||
|
||||
// --exclude-assets
|
||||
// --include-assets
|
||||
if (m_argument_parser.IsOptionSpecified(OPTION_EXCLUDE_ASSETS) && m_argument_parser.IsOptionSpecified(OPTION_INCLUDE_ASSETS))
|
||||
{
|
||||
con::error("You can only asset types to either exclude or include, not both");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_argument_parser.IsOptionSpecified(OPTION_EXCLUDE_ASSETS))
|
||||
{
|
||||
m_asset_type_handling = AssetTypeHandling::EXCLUDE;
|
||||
for (const auto& exclude : m_argument_parser.GetParametersForOption(OPTION_EXCLUDE_ASSETS))
|
||||
ParseCommaSeparatedAssetTypeString(exclude);
|
||||
}
|
||||
else if (m_argument_parser.IsOptionSpecified(OPTION_INCLUDE_ASSETS))
|
||||
{
|
||||
m_asset_type_handling = AssetTypeHandling::INCLUDE;
|
||||
for (const auto& include : m_argument_parser.GetParametersForOption(OPTION_INCLUDE_ASSETS))
|
||||
ParseCommaSeparatedAssetTypeString(include);
|
||||
}
|
||||
|
||||
// --legacy-menus
|
||||
if (m_argument_parser.IsOptionSpecified(OPTION_LEGACY_MENUS))
|
||||
ObjWriting::Configuration.MenuLegacyMode = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string UnlinkerArgs::GetOutputFolderPathForZone(const Zone& zone) const
|
||||
{
|
||||
return std::regex_replace(m_output_folder, m_zone_pattern, zone.m_name);
|
||||
}
|
||||
70
src/Unlinking/UnlinkerArgs.h
Normal file
70
src/Unlinking/UnlinkerArgs.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include "Utils/Arguments/ArgumentParser.h"
|
||||
#include "Zone/Zone.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <regex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class UnlinkerArgs
|
||||
{
|
||||
public:
|
||||
static constexpr const char* DEFAULT_OUTPUT_FOLDER = "zone_dump/zone_raw/?zone?";
|
||||
|
||||
private:
|
||||
ArgumentParser m_argument_parser;
|
||||
std::regex m_zone_pattern;
|
||||
|
||||
/**
|
||||
* \brief Prints a command line usage help text for the Unlinker tool to stdout.
|
||||
*/
|
||||
void PrintUsage() const;
|
||||
static void PrintVersion();
|
||||
bool SetImageDumpingMode() const;
|
||||
bool SetModelDumpingMode() const;
|
||||
|
||||
void AddSpecifiedAssetType(std::string value);
|
||||
void ParseCommaSeparatedAssetTypeString(const std::string& input);
|
||||
|
||||
public:
|
||||
enum class ProcessingTask
|
||||
{
|
||||
DUMP,
|
||||
LIST
|
||||
};
|
||||
|
||||
enum class AssetTypeHandling : std::uint8_t
|
||||
{
|
||||
EXCLUDE,
|
||||
INCLUDE
|
||||
};
|
||||
|
||||
std::vector<std::string> m_zones_to_load;
|
||||
std::vector<std::string> m_zones_to_unlink;
|
||||
std::set<std::string> m_user_search_paths;
|
||||
|
||||
ProcessingTask m_task;
|
||||
std::string m_output_folder;
|
||||
bool m_minimal_zone_def;
|
||||
|
||||
std::vector<std::string> m_specified_asset_types;
|
||||
std::unordered_map<std::string, size_t> m_specified_asset_type_map;
|
||||
AssetTypeHandling m_asset_type_handling;
|
||||
|
||||
bool m_skip_obj;
|
||||
bool m_use_gdt;
|
||||
|
||||
UnlinkerArgs();
|
||||
bool ParseArgs(int argc, const char** argv, bool& shouldContinue);
|
||||
|
||||
/**
|
||||
* \brief Converts the output path specified by command line arguments to a path applies for the specified zone.
|
||||
* \param zone The zone to resolve the path input for.
|
||||
* \return An output path for the zone based on the user input.
|
||||
*/
|
||||
std::string GetOutputFolderPathForZone(const Zone& zone) const;
|
||||
};
|
||||
91
src/Unlinking/UnlinkerPaths.cpp
Normal file
91
src/Unlinking/UnlinkerPaths.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "UnlinkerPaths.h"
|
||||
|
||||
#include "SearchPath/IWD.h"
|
||||
#include "SearchPath/SearchPathFilesystem.h"
|
||||
#include "Utils/Logging/Log.h"
|
||||
#include "Utils/StringUtils.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
bool UnlinkerPaths::LoadUserPaths(const UnlinkerArgs& args)
|
||||
{
|
||||
for (const auto& path : args.m_user_search_paths)
|
||||
{
|
||||
auto absolutePath = fs::absolute(path);
|
||||
|
||||
if (!fs::is_directory(absolutePath))
|
||||
{
|
||||
con::error("Could not find directory of search path: \"{}\"", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto searchPathName = absolutePath.string();
|
||||
m_user_paths.CommitSearchPath(std::make_unique<SearchPathFilesystem>(searchPathName));
|
||||
m_specified_user_paths.emplace(std::move(searchPathName));
|
||||
|
||||
std::filesystem::directory_iterator iterator(absolutePath);
|
||||
const auto end = fs::end(iterator);
|
||||
for (auto i = fs::begin(iterator); i != end; ++i)
|
||||
{
|
||||
if (!i->is_regular_file())
|
||||
continue;
|
||||
|
||||
auto extension = i->path().extension().string();
|
||||
utils::MakeStringLowerCase(extension);
|
||||
if (extension == ".iwd")
|
||||
{
|
||||
auto iwd = iwd::LoadFromFile(i->path().string());
|
||||
if (iwd)
|
||||
m_user_paths.CommitSearchPath(std::move(iwd));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
con::info("{} SearchPaths{}", m_specified_user_paths.size(), !m_specified_user_paths.empty() ? ":" : "");
|
||||
for (const auto& absoluteSearchPath : m_specified_user_paths)
|
||||
con::info(" \"{}\"", absoluteSearchPath);
|
||||
|
||||
if (!m_specified_user_paths.empty())
|
||||
con::info("");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<ISearchPath> UnlinkerPaths::GetSearchPathsForZone(const std::string& zonePath)
|
||||
{
|
||||
const auto absoluteZoneDirectory = fs::absolute(std::filesystem::path(zonePath).remove_filename());
|
||||
const auto absoluteZoneDirectoryString = absoluteZoneDirectory.string();
|
||||
if (m_last_zone_path != absoluteZoneDirectoryString)
|
||||
{
|
||||
m_last_zone_path = absoluteZoneDirectoryString;
|
||||
m_last_zone_search_paths = SearchPaths();
|
||||
m_last_zone_search_paths.CommitSearchPath(std::make_unique<SearchPathFilesystem>(absoluteZoneDirectoryString));
|
||||
|
||||
std::filesystem::directory_iterator iterator(absoluteZoneDirectory);
|
||||
const auto end = fs::end(iterator);
|
||||
for (auto i = fs::begin(iterator); i != end; ++i)
|
||||
{
|
||||
if (!i->is_regular_file())
|
||||
continue;
|
||||
|
||||
auto extension = i->path().extension().string();
|
||||
utils::MakeStringLowerCase(extension);
|
||||
if (extension == ".iwd")
|
||||
{
|
||||
auto iwd = iwd::LoadFromFile(i->path().string());
|
||||
if (iwd)
|
||||
m_last_zone_search_paths.CommitSearchPath(std::move(iwd));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto result = std::make_unique<SearchPaths>();
|
||||
result->IncludeSearchPath(&m_last_zone_search_paths);
|
||||
result->IncludeSearchPath(&m_user_paths);
|
||||
|
||||
return result;
|
||||
}
|
||||
21
src/Unlinking/UnlinkerPaths.h
Normal file
21
src/Unlinking/UnlinkerPaths.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "SearchPath/SearchPaths.h"
|
||||
#include "UnlinkerArgs.h"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
class UnlinkerPaths
|
||||
{
|
||||
public:
|
||||
bool LoadUserPaths(const UnlinkerArgs& args);
|
||||
std::unique_ptr<ISearchPath> GetSearchPathsForZone(const std::string& zonePath);
|
||||
|
||||
private:
|
||||
std::string m_last_zone_path;
|
||||
SearchPaths m_last_zone_search_paths;
|
||||
|
||||
std::unordered_set<std::string> m_specified_user_paths;
|
||||
SearchPaths m_user_paths;
|
||||
};
|
||||
Reference in New Issue
Block a user