2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2026-07-03 22:30:07 +00:00

feat: initial T4 support (#807)

* feat: initial T4 support

* chore: adjust t4 symbols a bit for accuracy

* chore: add PackIndex asset to T4

* chore: remove unused AssetXModelPieces

* chore: add default and global asset pools loader for T4

* chore: use separate defines for T4 in ImageDumper

* chore: remove unnecessary namespaces in gfximage_actions

* chore: small things

* chore: fix T4 PhysPreset type

* chore: use proper XQuat2 type for T4 xanims

* chore: fix errors on T4 types

* chore: use iw3 like struct for XModelStreamInfo

* docs: add basic docs for T4

* chore: add basic ObjCompiler setup for T4

* chore: adjust loaded sound definition

* chore: make sure t4 material has the correct alignment

* chore: make sure t4 uses similar names for assets as other games

* fix: asset references should not be reusable

* chore: add content writer for t4

* feat: add t4 localize loader

* chore: reorder game ids to be alphabetically ordered

---------

Co-authored-by: Jan Laupetin <jan@laupetin.net>
This commit is contained in:
mo
2026-06-07 13:06:33 +01:00
committed by GitHub
parent 04628fc52c
commit 44d6710991
88 changed files with 6787 additions and 18 deletions
@@ -17,7 +17,7 @@ void Actions_GfxImage::OnImageLoaded(GfxImage* image) const
void Actions_GfxImage::LoadImageData(GfxImageLoadDef* loadDef, GfxImage* image) const
{
const size_t loadDefSize = offsetof(IW3::GfxImageLoadDef, data) + loadDef->resourceSize;
const size_t loadDefSize = offsetof(GfxImageLoadDef, data) + loadDef->resourceSize;
image->texture.loadDef = static_cast<GfxImageLoadDef*>(m_zone.Memory().AllocRaw(loadDefSize));
memcpy(image->texture.loadDef, loadDef, loadDefSize);
@@ -17,7 +17,7 @@ void Actions_GfxImage::OnImageLoaded(GfxImage* image) const
void Actions_GfxImage::LoadImageData(GfxImageLoadDef* loadDef, GfxImage* image) const
{
const size_t loadDefSize = offsetof(IW4::GfxImageLoadDef, data) + loadDef->resourceSize;
const size_t loadDefSize = offsetof(GfxImageLoadDef, data) + loadDef->resourceSize;
image->texture.loadDef = static_cast<GfxImageLoadDef*>(m_zone.Memory().AllocRaw(loadDefSize));
memcpy(image->texture.loadDef, loadDef, loadDefSize);
@@ -17,7 +17,7 @@ void Actions_GfxImage::OnImageLoaded(GfxImage* image) const
void Actions_GfxImage::LoadImageData(GfxImageLoadDef* loadDef, GfxImage* image) const
{
const size_t loadDefSize = offsetof(IW5::GfxImageLoadDef, data) + loadDef->resourceSize;
const size_t loadDefSize = offsetof(GfxImageLoadDef, data) + loadDef->resourceSize;
image->texture.loadDef = static_cast<GfxImageLoadDef*>(m_zone.Memory().AllocRaw(loadDefSize));
memcpy(image->texture.loadDef, loadDef, loadDefSize);
+174
View File
@@ -0,0 +1,174 @@
#include "ContentLoaderT4.h"
#include "Game/T4/AssetLoaderT4.h"
#include "Game/T4/T4.h"
#include "Loading/Exception/UnsupportedAssetTypeException.h"
#include <cassert>
using namespace T4;
ContentLoader::ContentLoader(Zone& zone, ZoneInputStream& stream)
: ContentLoaderBase(zone, stream),
varXAssetList(nullptr),
varXAsset(nullptr),
varScriptStringList(nullptr)
{
}
void ContentLoader::LoadScriptStringList(const bool atStreamStart)
{
assert(!atStreamStart);
if (varScriptStringList->strings != nullptr)
{
assert(GetZonePointerType(varScriptStringList->strings) == ZonePointerType::FOLLOWING);
#ifdef ARCH_x86
varScriptStringList->strings = m_stream.Alloc<const char*>(4);
#else
varScriptStringList->strings = m_stream.AllocOutOfBlock<const char*>(4, varScriptStringList->count);
#endif
varXString = varScriptStringList->strings;
LoadXStringArray(true, varScriptStringList->count);
if (varScriptStringList->strings && varScriptStringList->count > 0)
m_zone.m_script_strings.InitializeForExistingZone(varScriptStringList->strings, static_cast<size_t>(varScriptStringList->count));
}
assert(m_zone.m_script_strings.Count() <= SCR_STRING_MAX + 1);
}
void ContentLoader::LoadXAsset(const bool atStreamStart) const
{
#define LOAD_ASSET(type_index, typeName, headerEntry) \
case type_index: \
{ \
Loader_##typeName loader(m_zone, m_stream); \
loader.Load(&varXAsset->header.headerEntry); \
break; \
}
#define SKIP_ASSET(type_index, typeName, headerEntry) \
case type_index: \
break;
assert(varXAsset != nullptr);
if (atStreamStart)
m_stream.Load<XAsset>(varXAsset);
switch (varXAsset->type)
{
SKIP_ASSET(ASSET_TYPE_XMODELPIECES, XModelPieces, data)
LOAD_ASSET(ASSET_TYPE_PHYSPRESET, PhysPreset, physPreset)
LOAD_ASSET(ASSET_TYPE_PHYSCONSTRAINTS, PhysConstraints, physConstraints)
LOAD_ASSET(ASSET_TYPE_DESTRUCTIBLEDEF, DestructibleDef, destructibleDef)
LOAD_ASSET(ASSET_TYPE_XANIMPARTS, XAnimParts, parts)
LOAD_ASSET(ASSET_TYPE_XMODEL, XModel, model)
LOAD_ASSET(ASSET_TYPE_MATERIAL, Material, material)
LOAD_ASSET(ASSET_TYPE_TECHNIQUE_SET, MaterialTechniqueSet, techniqueSet)
LOAD_ASSET(ASSET_TYPE_IMAGE, GfxImage, image)
LOAD_ASSET(ASSET_TYPE_SOUND, snd_alias_list_t, sound)
LOAD_ASSET(ASSET_TYPE_LOADED_SOUND, LoadedSound, loadSnd)
LOAD_ASSET(ASSET_TYPE_CLIPMAP, clipMap_t, clipMap)
LOAD_ASSET(ASSET_TYPE_CLIPMAP_PVS, clipMap_t, clipMap)
LOAD_ASSET(ASSET_TYPE_COMWORLD, ComWorld, comWorld)
LOAD_ASSET(ASSET_TYPE_GAMEWORLD_SP, GameWorldSp, gameWorldSp)
LOAD_ASSET(ASSET_TYPE_GAMEWORLD_MP, GameWorldMp, gameWorldMp)
LOAD_ASSET(ASSET_TYPE_MAP_ENTS, MapEnts, mapEnts)
LOAD_ASSET(ASSET_TYPE_GFXWORLD, GfxWorld, gfxWorld)
LOAD_ASSET(ASSET_TYPE_LIGHT_DEF, GfxLightDef, lightDef)
SKIP_ASSET(ASSET_TYPE_UI_MAP, UiMap, data)
LOAD_ASSET(ASSET_TYPE_FONT, Font_s, font)
LOAD_ASSET(ASSET_TYPE_MENULIST, MenuList, menuList)
LOAD_ASSET(ASSET_TYPE_MENU, menuDef_t, menu)
LOAD_ASSET(ASSET_TYPE_LOCALIZE_ENTRY, LocalizeEntry, localize)
LOAD_ASSET(ASSET_TYPE_WEAPON, WeaponDef, weapon)
LOAD_ASSET(ASSET_TYPE_SNDDRIVER_GLOBALS, SndDriverGlobals, sndDriverGlobals)
LOAD_ASSET(ASSET_TYPE_FX, FxEffectDef, fx)
LOAD_ASSET(ASSET_TYPE_IMPACT_FX, FxImpactTable, impactFx)
SKIP_ASSET(ASSET_TYPE_AITYPE, AiType, data)
SKIP_ASSET(ASSET_TYPE_MPTYPE, MpType, data)
SKIP_ASSET(ASSET_TYPE_CHARACTER, Character, data)
SKIP_ASSET(ASSET_TYPE_XMODELALIAS, XModelAlias, data)
LOAD_ASSET(ASSET_TYPE_RAWFILE, RawFile, rawfile)
LOAD_ASSET(ASSET_TYPE_STRINGTABLE, StringTable, stringTable)
LOAD_ASSET(ASSET_TYPE_PACK_INDEX, PackIndex, packIndex)
default:
{
throw UnsupportedAssetTypeException(varXAsset->type);
}
}
#undef LOAD_ASSET
}
void ContentLoader::LoadXAssetArray(const bool atStreamStart, const size_t count)
{
assert(varXAsset != nullptr);
if (atStreamStart)
{
#ifdef ARCH_x86
m_stream.Load<XAsset>(varXAsset, count);
#else
const auto fill = m_stream.LoadWithFill(8u * count);
for (size_t index = 0; index < count; index++)
{
fill.Fill(varXAsset[index].type, 8u * index);
fill.FillPtr(varXAsset[index].header.data, 8u * index + 4u);
m_stream.AddPointerLookup(&varXAsset[index].header.data, fill.BlockBuffer(8u * index + 4u));
}
#endif
}
for (size_t index = 0; index < count; index++)
{
LoadXAsset(false);
varXAsset++;
#ifdef DEBUG_OFFSETS
m_stream.DebugOffsets(index);
#endif
}
}
void ContentLoader::Load()
{
XAssetList assetList{};
varXAssetList = &assetList;
#ifdef ARCH_x86
m_stream.LoadDataRaw(&assetList, sizeof(assetList));
#else
const auto fillAccessor = m_stream.LoadWithFill(16u);
varScriptStringList = &varXAssetList->stringList;
fillAccessor.Fill(varScriptStringList->count, 0u);
fillAccessor.FillPtr(varScriptStringList->strings, 4u);
fillAccessor.Fill(varXAssetList->assetCount, 8u);
fillAccessor.FillPtr(varXAssetList->assets, 12u);
#endif
m_stream.PushBlock(XFILE_BLOCK_VIRTUAL);
varScriptStringList = &assetList.stringList;
LoadScriptStringList(false);
if (assetList.assets != nullptr)
{
assert(GetZonePointerType(assetList.assets) == ZonePointerType::FOLLOWING);
#ifdef ARCH_x86
assetList.assets = m_stream.Alloc<XAsset>(4);
#else
assetList.assets = m_stream.AllocOutOfBlock<XAsset>(4, assetList.assetCount);
#endif
varXAsset = assetList.assets;
LoadXAssetArray(true, assetList.assetCount);
}
m_stream.PopBlock();
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include "Game/T4/T4.h"
#include "Loading/ContentLoaderBase.h"
#include "Loading/IContentLoadingEntryPoint.h"
namespace T4
{
class ContentLoader final : public ContentLoaderBase, public IContentLoadingEntryPoint
{
public:
ContentLoader(Zone& zone, ZoneInputStream& stream);
void Load() override;
private:
void LoadScriptStringList(bool atStreamStart);
void LoadXAsset(bool atStreamStart) const;
void LoadXAssetArray(bool atStreamStart, size_t count);
XAssetList* varXAssetList;
XAsset* varXAsset;
ScriptStringList* varScriptStringList;
};
} // namespace T4
@@ -0,0 +1,24 @@
#include "gfximage_actions.h"
#include <cassert>
#include <cstring>
using namespace T4;
Actions_GfxImage::Actions_GfxImage(Zone& zone)
: AssetLoadingActions(zone)
{
}
void Actions_GfxImage::OnImageLoaded(GfxImage* image) const
{
image->cardMemory.platform[0] = 0;
}
void Actions_GfxImage::LoadImageData(GfxImageLoadDef* loadDef, GfxImage* image) const
{
const size_t loadDefSize = offsetof(GfxImageLoadDef, data) + loadDef->resourceSize;
image->texture.loadDef = static_cast<GfxImageLoadDef*>(m_zone.Memory().AllocRaw(loadDefSize));
memcpy(image->texture.loadDef, loadDef, loadDefSize);
}
@@ -0,0 +1,16 @@
#pragma once
#include "Game/T4/T4.h"
#include "Loading/AssetLoadingActions.h"
namespace T4
{
class Actions_GfxImage final : public AssetLoadingActions
{
public:
explicit Actions_GfxImage(Zone& zone);
void OnImageLoaded(GfxImage* image) const;
void LoadImageData(GfxImageLoadDef* loadDef, GfxImage* image) const;
};
} // namespace T4
@@ -0,0 +1,89 @@
#include "ZoneLoaderFactoryT4.h"
#include "ContentLoaderT4.h"
#include "Game/GameLanguage.h"
#include "Game/T4/T4.h"
#include "Game/T4/ZoneConstantsT4.h"
#include "Loading/Processor/ProcessorInflate.h"
#include "Loading/Steps/StepAddProcessor.h"
#include "Loading/Steps/StepAllocXBlocks.h"
#include "Loading/Steps/StepLoadZoneContent.h"
#include "Loading/Steps/StepLoadZoneSizes.h"
#include "Utils/ClassUtils.h"
#include "Utils/Endianness.h"
#include <cstring>
using namespace T4;
namespace
{
void SetupBlock(ZoneLoader& zoneLoader)
{
#define XBLOCK_DEF(name, type) std::make_unique<XBlock>(STR(name), name, type)
zoneLoader.AddXBlock(XBLOCK_DEF(T4::XFILE_BLOCK_TEMP, XBlockType::BLOCK_TYPE_TEMP));
zoneLoader.AddXBlock(XBLOCK_DEF(T4::XFILE_BLOCK_RUNTIME, XBlockType::BLOCK_TYPE_RUNTIME));
zoneLoader.AddXBlock(XBLOCK_DEF(T4::XFILE_BLOCK_LARGE_RUNTIME, XBlockType::BLOCK_TYPE_RUNTIME));
zoneLoader.AddXBlock(XBLOCK_DEF(T4::XFILE_BLOCK_PHYSICAL_RUNTIME, XBlockType::BLOCK_TYPE_RUNTIME));
zoneLoader.AddXBlock(XBLOCK_DEF(T4::XFILE_BLOCK_VIRTUAL, XBlockType::BLOCK_TYPE_NORMAL));
zoneLoader.AddXBlock(XBLOCK_DEF(T4::XFILE_BLOCK_LARGE, XBlockType::BLOCK_TYPE_NORMAL));
zoneLoader.AddXBlock(XBLOCK_DEF(T4::XFILE_BLOCK_PHYSICAL, XBlockType::BLOCK_TYPE_NORMAL));
#undef XBLOCK_DEF
}
} // namespace
std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(const ZoneHeader& header) const
{
if (endianness::FromLittleEndian(header.m_version) == ZoneConstants::ZONE_VERSION_PC
&& !memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, std::char_traits<char>::length(ZoneConstants::MAGIC_UNSIGNED)))
{
return ZoneLoaderInspectionResult{
.m_game_id = GameId::T4,
.m_endianness = GameEndianness::LE,
.m_word_size = GameWordSize::ARCH_32,
.m_platform = GamePlatform::PC,
// There is no way to know whether unsigned zones are official.
.m_is_official = false,
.m_is_signed = false,
.m_is_encrypted = false,
};
}
return std::nullopt;
}
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneHeader& header,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const
{
const auto inspectResult = InspectZoneHeader(header);
if (!inspectResult)
return nullptr;
// Create new zone
auto zone = std::make_unique<Zone>(fileName, 0, GameId::T4, inspectResult->m_platform);
auto* zonePtr = zone.get();
zone->m_language = GameLanguage::LANGUAGE_NONE;
// File is supported. Now setup all required steps for loading this file.
auto zoneLoader = std::make_unique<ZoneLoader>(std::move(zone));
SetupBlock(*zoneLoader);
zoneLoader->AddLoadingStep(step::CreateStepAddProcessor(processor::CreateProcessorInflate(ZoneConstants::AUTHED_CHUNK_SIZE)));
zoneLoader->AddLoadingStep(step::CreateStepLoadZoneSizes());
zoneLoader->AddLoadingStep(step::CreateStepAllocXBlocks());
zoneLoader->AddLoadingStep(step::CreateStepLoadZoneContent(
[zonePtr](ZoneInputStream& stream)
{
return std::make_unique<ContentLoader>(*zonePtr, stream);
},
32u,
ZoneConstants::OFFSET_BLOCK_BIT_COUNT,
ZoneConstants::INSERT_BLOCK,
zonePtr->Memory(),
std::move(progressCallback)));
return zoneLoader;
}
@@ -0,0 +1,17 @@
#pragma once
#include "Loading/IZoneLoaderFactory.h"
#include <string>
namespace T4
{
class ZoneLoaderFactory final : public IZoneLoaderFactory
{
public:
[[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(const ZoneHeader& header) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(const ZoneHeader& header,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const override;
};
} // namespace T4
@@ -17,7 +17,7 @@ void Actions_GfxImage::OnImageLoaded(GfxImage* image) const
void Actions_GfxImage::LoadImageData(GfxImageLoadDef* loadDef, GfxImage* image) const
{
const size_t loadDefSize = offsetof(T6::GfxImageLoadDef, data) + loadDef->resourceSize;
const size_t loadDefSize = offsetof(GfxImageLoadDef, data) + loadDef->resourceSize;
image->texture.loadDef = static_cast<GfxImageLoadDef*>(m_zone.Memory().AllocRaw(loadDefSize));
memcpy(image->texture.loadDef, loadDef, loadDefSize);
@@ -3,6 +3,7 @@
#include "Game/IW3/ZoneLoaderFactoryIW3.h"
#include "Game/IW4/ZoneLoaderFactoryIW4.h"
#include "Game/IW5/ZoneLoaderFactoryIW5.h"
#include "Game/T4/ZoneLoaderFactoryT4.h"
#include "Game/T5/ZoneLoaderFactoryT5.h"
#include "Game/T6/ZoneLoaderFactoryT6.h"
@@ -10,10 +11,11 @@
const IZoneLoaderFactory* IZoneLoaderFactory::GetZoneLoaderFactoryForGame(GameId game)
{
static const IZoneLoaderFactory* zoneCreators[static_cast<unsigned>(GameId::COUNT)]{
static const IZoneLoaderFactory* zoneCreators[]{
new IW3::ZoneLoaderFactory(),
new IW4::ZoneLoaderFactory(),
new IW5::ZoneLoaderFactory(),
new T4::ZoneLoaderFactory(),
new T5::ZoneLoaderFactory(),
new T6::ZoneLoaderFactory(),
};