Fixed some missed dependencies in a few formats and implemented support for building file lists for paks and MLVLs, and implemented support for package cooking for MP1

This commit is contained in:
parax0
2016-08-12 04:27:19 -06:00
parent 0f2c0d5b39
commit de18044ae0
39 changed files with 1094 additions and 171 deletions

View File

@@ -115,16 +115,15 @@ CAssetID CDependencyTree::DependencyByIndex(u32 Index) const
return mReferencedResources[Index]->ID();
}
void CDependencyTree::AddDependency(CResource *pRes)
void CDependencyTree::AddDependency(CResource *pRes, bool AvoidDuplicates /*= true*/)
{
if (!pRes || HasDependency(pRes->ID())) return;
CResourceDependency *pDepend = new CResourceDependency(pRes->ID());
mReferencedResources.push_back(pDepend);
if (!pRes) return;
AddDependency(pRes->ID(), AvoidDuplicates);
}
void CDependencyTree::AddDependency(const CAssetID& rkID)
void CDependencyTree::AddDependency(const CAssetID& rkID, bool AvoidDuplicates /*= true*/)
{
if (!rkID.IsValid() || HasDependency(rkID)) return;
if (!rkID.IsValid() || (AvoidDuplicates && HasDependency(rkID))) return;
CResourceDependency *pDepend = new CResourceDependency(rkID);
mReferencedResources.push_back(pDepend);
}
@@ -154,14 +153,15 @@ void CAnimSetDependencyTree::Write(IOutputStream& rFile, EIDLength IDLength) con
rFile.WriteLong( mCharacterOffsets[iChar] );
}
void CAnimSetDependencyTree::AddCharacter(const SSetCharacter *pkChar)
void CAnimSetDependencyTree::AddCharacter(const SSetCharacter *pkChar, const std::set<CAssetID>& rkBaseUsedSet)
{
mCharacterOffsets.push_back( NumDependencies() );
if (!pkChar) return;
AddDependency(pkChar->pModel);
AddDependency(pkChar->pSkeleton);
AddDependency(pkChar->pSkin);
std::set<CAssetID> UsedSet = rkBaseUsedSet;
AddCharDependency(pkChar->pModel, UsedSet);
AddCharDependency(pkChar->pSkeleton, UsedSet);
AddCharDependency(pkChar->pSkin, UsedSet);
const std::vector<CAssetID> *pkParticleVectors[5] = {
&pkChar->GenericParticles, &pkChar->ElectricParticles,
@@ -172,11 +172,26 @@ void CAnimSetDependencyTree::AddCharacter(const SSetCharacter *pkChar)
for (u32 iVec = 0; iVec < 5; iVec++)
{
for (u32 iPart = 0; iPart < pkParticleVectors[iVec]->size(); iPart++)
AddDependency(pkParticleVectors[iVec]->at(iPart));
AddCharDependency(pkParticleVectors[iVec]->at(iPart), UsedSet);
}
AddDependency(pkChar->IceModel);
AddDependency(pkChar->IceSkin);
AddCharDependency(pkChar->IceModel, UsedSet);
AddCharDependency(pkChar->IceSkin, UsedSet);
}
void CAnimSetDependencyTree::AddCharDependency(const CAssetID& rkID, std::set<CAssetID>& rUsedSet)
{
if (rkID.IsValid() && rUsedSet.find(rkID) == rUsedSet.end())
{
rUsedSet.insert(rkID);
AddDependency(rkID, false);
}
}
void CAnimSetDependencyTree::AddCharDependency(CResource *pRes, std::set<CAssetID>& rUsedSet)
{
if (!pRes) return;
AddCharDependency(pRes->ID(), rUsedSet);
}
// ************ CScriptInstanceDependencyTree ************
@@ -240,6 +255,12 @@ bool CScriptInstanceDependencyTree::HasDependency(const CAssetID& rkID)
return false;
}
CAssetID CScriptInstanceDependencyTree::DependencyByIndex(u32 Index) const
{
ASSERT(Index >= 0 && Index < mDependencies.size());
return mDependencies[Index]->ID();
}
// Static
CScriptInstanceDependencyTree* CScriptInstanceDependencyTree::BuildTree(CScriptObject *pInstance)
{
@@ -343,3 +364,9 @@ void CAreaDependencyTree::AddScriptLayer(CScriptLayer *pLayer)
delete pTree;
}
}
CScriptInstanceDependencyTree* CAreaDependencyTree::ScriptInstanceByIndex(u32 Index) const
{
ASSERT(Index >= 0 && Index < mScriptInstances.size());
return mScriptInstances[Index];
}

View File

@@ -90,8 +90,8 @@ public:
u32 NumDependencies() const;
bool HasDependency(const CAssetID& rkID);
CAssetID DependencyByIndex(u32 Index) const;
void AddDependency(const CAssetID& rkID);
void AddDependency(CResource *pRes);
void AddDependency(const CAssetID& rkID, bool AvoidDuplicates = true);
void AddDependency(CResource *pRes, bool AvoidDuplicates = true);
// Accessors
inline void SetID(const CAssetID& rkID) { mID = rkID; }
@@ -111,7 +111,13 @@ public:
virtual void Read(IInputStream& rFile, EIDLength IDLength);
virtual void Write(IOutputStream& rFile, EIDLength IDLength) const;
void AddCharacter(const SSetCharacter *pkChar);
void AddCharacter(const SSetCharacter *pkChar, const std::set<CAssetID>& rkBaseUsedSet);
void AddCharDependency(const CAssetID& rkID, std::set<CAssetID>& rUsedSet);
void AddCharDependency(CResource *pRes, std::set<CAssetID>& rUsedSet);
// Accessors
inline u32 NumCharacters() const { return mCharacterOffsets.size(); }
inline u32 CharacterOffset(u32 CharIdx) const { return mCharacterOffsets[CharIdx]; }
};
// Node representing a script object. Indicates the type of object.
@@ -128,9 +134,11 @@ public:
virtual void Read(IInputStream& rFile, EIDLength IDLength);
virtual void Write(IOutputStream& rFile, EIDLength IDLength) const;
bool HasDependency(const CAssetID& rkID);
CAssetID DependencyByIndex(u32 Index) const;
// Accessors
u32 NumDependencies() const { return mDependencies.size(); }
inline u32 NumDependencies() const { return mDependencies.size(); }
inline u32 ObjectType() const { return mObjectType; }
// Static
static CScriptInstanceDependencyTree* BuildTree(CScriptObject *pInstance);
@@ -153,6 +161,12 @@ public:
virtual void Write(IOutputStream& rFile, EIDLength IDLength) const;
void AddScriptLayer(CScriptLayer *pLayer);
CScriptInstanceDependencyTree* ScriptInstanceByIndex(u32 Index) const;
// Accessors
inline u32 NumScriptLayers() const { return mLayerOffsets.size(); }
inline u32 NumScriptInstances() const { return mScriptInstances.size(); }
inline u32 ScriptLayerOffset(u32 LayerIdx) const { return mLayerOffsets[LayerIdx]; }
};
#endif // CDEPENDENCYTREE

View File

@@ -148,7 +148,7 @@ void CGameExporter::LoadAssetList()
while (pAsset)
{
u64 ResourceID = TString(pAsset->Attribute("ID")).ToInt64(16);
CAssetID ResourceID = TString(pAsset->Attribute("ID")).ToInt64(16);
tinyxml2::XMLElement *pDir = pAsset->FirstChildElement("Dir");
TString Dir = pDir ? pDir->GetText() : "";
@@ -206,11 +206,15 @@ void CGameExporter::LoadPaks()
u32 NameLen = Pak.ReadLong();
TString Name = Pak.ReadString(NameLen);
pCollection->AddResource(Name, ResID, ResType);
SetResourcePath(ResID.ToLongLong(), PakName + L"\\", Name.ToUTF16());
SetResourcePath(ResID, PakName + L"\\", Name.ToUTF16());
}
u32 NumResources = Pak.ReadLong();
// Keep track of which areas have duplicate resources
std::set<CAssetID> PakResourceSet;
bool AreaHasDuplicates = true; // Default to true so that first area is always considered as having duplicates
for (u32 iRes = 0; iRes < NumResources; iRes++)
{
bool Compressed = (Pak.ReadLong() == 1);
@@ -219,9 +223,21 @@ void CGameExporter::LoadPaks()
u32 ResSize = Pak.ReadLong();
u32 ResOffset = Pak.ReadLong();
u64 IntegralID = ResID.ToLongLong();
if (mResourceMap.find(IntegralID) == mResourceMap.end())
mResourceMap[IntegralID] = SResourceInstance { PakPath, ResID, ResType, ResOffset, ResSize, Compressed, false };
if (mResourceMap.find(ResID) == mResourceMap.end())
mResourceMap[ResID] = SResourceInstance { PakPath, ResID, ResType, ResOffset, ResSize, Compressed, false };
// Check for duplicate resources
if (ResType == "MREA")
{
mAreaDuplicateMap[ResID] = AreaHasDuplicates;
AreaHasDuplicates = false;
}
else if (!AreaHasDuplicates && PakResourceSet.find(ResID) != PakResourceSet.end())
AreaHasDuplicates = true;
else
PakResourceSet.insert(ResID);
}
}
}
@@ -265,7 +281,7 @@ void CGameExporter::LoadPaks()
CFourCC ResType = Pak.ReadLong();
CAssetID ResID(Pak, IDLength);
pCollection->AddResource(Name, ResID, ResType);
SetResourcePath(ResID.ToLongLong(), PakName + L"\\", Name.ToUTF16());
SetResourcePath(ResID, PakName + L"\\", Name.ToUTF16());
}
}
@@ -275,6 +291,10 @@ void CGameExporter::LoadPaks()
u32 DataStart = Next;
u32 NumResources = Pak.ReadLong();
// Keep track of which areas have duplicate resources
std::set<CAssetID> PakResourceSet;
bool AreaHasDuplicates = true; // Default to true so that first area is always considered as having duplicates
for (u32 iRes = 0; iRes < NumResources; iRes++)
{
bool Compressed = (Pak.ReadLong() == 1);
@@ -283,9 +303,24 @@ void CGameExporter::LoadPaks()
u32 Size = Pak.ReadLong();
u32 Offset = DataStart + Pak.ReadLong();
u64 IntegralID = ResID.ToLongLong();
if (mResourceMap.find(IntegralID) == mResourceMap.end())
mResourceMap[IntegralID] = SResourceInstance { PakPath, ResID, Type, Offset, Size, Compressed, false };
if (mResourceMap.find(ResID) == mResourceMap.end())
mResourceMap[ResID] = SResourceInstance { PakPath, ResID, Type, Offset, Size, Compressed, false };
// Check for duplicate resources (unnecessary for DKCR)
if (Game() != eReturns)
{
if (Type == "MREA")
{
mAreaDuplicateMap[ResID] = AreaHasDuplicates;
AreaHasDuplicates = false;
}
else if (!AreaHasDuplicates && PakResourceSet.find(ResID) != PakResourceSet.end())
AreaHasDuplicates = true;
else
PakResourceSet.insert(ResID);
}
}
}
@@ -505,7 +540,25 @@ void CGameExporter::ExportCookedResources()
for (CResourceIterator It(&mStore); It; ++It)
{
if (!It->IsTransient())
{
// Worlds need to know which areas can have duplicates. We only have this info at export time.
if (It->ResourceType() == eWorld)
{
CWorld *pWorld = (CWorld*) It->Load();
for (u32 iArea = 0; iArea < pWorld->NumAreas(); iArea++)
{
CAssetID AreaID = pWorld->AreaResourceID(iArea);
auto Find = mAreaDuplicateMap.find(AreaID);
if (Find != mAreaDuplicateMap.end())
pWorld->SetAreaAllowsPakDuplicates(iArea, Find->second);
}
}
// Save raw resource + cache data
It->Save();
}
}
}
}

View File

@@ -26,6 +26,7 @@ class CGameExporter
// Resources
TWideStringList mPaks;
std::map<CAssetID, bool> mAreaDuplicateMap;
struct SResourceInstance
{
@@ -37,14 +38,14 @@ class CGameExporter
bool Compressed;
bool Exported;
};
std::map<u64, SResourceInstance> mResourceMap;
std::map<CAssetID, SResourceInstance> mResourceMap;
struct SResourcePath
{
TWideString Dir;
TWideString Name;
};
std::map<u64, SResourcePath> mResourcePaths;
std::map<CAssetID, SResourcePath> mResourcePaths;
public:
CGameExporter(const TString& rkInputDir, const TString& rkOutputDir);
@@ -77,12 +78,7 @@ protected:
inline void SetResourcePath(const CAssetID& rkID, const TWideString& rkDir, const TWideString& rkName)
{
SetResourcePath(rkID.ToLongLong(), rkDir, rkName);
}
inline void SetResourcePath(u64 ID, const TWideString& rkDir, const TWideString& rkName)
{
mResourcePaths[ID] = SResourcePath { rkDir, rkName };
mResourcePaths[rkID] = SResourcePath { rkDir, rkName };
}
inline EGame Game() const { return mpProject->Game(); }

View File

@@ -1,6 +1,10 @@
#include "CPackage.h"
#include "DependencyListBuilders.h"
#include "CGameProject.h"
#include "Core/Resource/Cooker/CWorldCooker.h"
#include <FileIO/FileIO.h>
#include <Common/AssertMacro.h>
#include <Common/CompressionUtil.h>
#include <Common/FileUtil.h>
#include <tinyxml2.h>
@@ -106,6 +110,244 @@ void CPackage::Save()
Log::Error("Failed to save pak definition at path: " + DefPath.ToUTF8());
}
void CPackage::Cook()
{
// Build asset list
CPackageDependencyListBuilder Builder(this);
std::list<CAssetID> AssetList;
Builder.BuildDependencyList(true, AssetList);
Log::Write(TString::FromInt32(AssetList.size(), 0, 10) + " assets in pak");
// Get named resources
std::list<const SNamedResource*> NamedResources;
for (u32 iCol = 0; iCol < mCollections.size(); iCol++)
{
CResourceCollection *pCol = mCollections[iCol];
for (u32 iRes = 0; iRes < pCol->NumResources(); iRes++)
NamedResources.push_back(&pCol->ResourceByIndex(iRes));
}
// Write new pak
TWideString PakPath = CookedPackagePath(false);
CFileOutStream Pak(PakPath.ToUTF8().ToStdString(), IOUtil::eBigEndian);
if (!Pak.IsValid())
{
Log::Error("Couldn't cook package " + CookedPackagePath(true).ToUTF8() + "; unable to open package for writing");
return;
}
// todo: MP3/DKCR pak format support
Pak.WriteLong(0x00030005); // Major/Minor Version
Pak.WriteLong(0); // Unknown
// Named Resources
Pak.WriteLong(NamedResources.size());
for (auto Iter = NamedResources.begin(); Iter != NamedResources.end(); Iter++)
{
const SNamedResource *pkRes = *Iter;
pkRes->Type.Write(Pak);
pkRes->ID.Write(Pak);
Pak.WriteLong(pkRes->Name.Size());
Pak.WriteString(pkRes->Name.ToStdString(), pkRes->Name.Size()); // Note: Explicitly specifying size means we don't write the terminating 0
// TEMP: recook world
if (pkRes->Type == "MLVL")
{
CResourceEntry *pEntry = gpResourceStore->FindEntry(pkRes->ID);
ASSERT(pEntry);
CWorld *pWorld = (CWorld*) pEntry->Load();
ASSERT(pWorld);
CFileOutStream MLVL(pEntry->CookedAssetPath().ToStdString(), IOUtil::eBigEndian);
ASSERT(MLVL.IsValid());
CWorldCooker::CookMLVL(pWorld, MLVL);
}
}
// Fill in table of contents with junk, write later
Pak.WriteLong(AssetList.size());
u32 TocOffset = Pak.Tell();
for (u32 iRes = 0; iRes < AssetList.size(); iRes++)
{
Pak.WriteLongLong(0);
Pak.WriteLongLong(0);
Pak.WriteLong(0);
}
Pak.WriteToBoundary(32, 0);
// Start writing resources
struct SResourceTocInfo
{
CResourceEntry *pEntry;
u32 Offset;
u32 Size;
bool Compressed;
};
std::vector<SResourceTocInfo> ResourceTocData(AssetList.size());
u32 ResIdx = 0;
for (auto Iter = AssetList.begin(); Iter != AssetList.end(); Iter++, ResIdx++)
{
CAssetID ID = *Iter;
CResourceEntry *pEntry = gpResourceStore->FindEntry(ID);
ASSERT(pEntry != nullptr);
SResourceTocInfo& rTocInfo = ResourceTocData[ResIdx];
rTocInfo.pEntry = pEntry;
rTocInfo.Offset = Pak.Tell();
// Load resource data
CFileInStream CookedAsset(pEntry->CookedAssetPath().ToStdString(), IOUtil::eBigEndian);
ASSERT(CookedAsset.IsValid());
u32 ResourceSize = CookedAsset.Size();
std::vector<u8> ResourceData(ResourceSize);
CookedAsset.ReadBytes(ResourceData.data(), ResourceData.size());
// Check if this asset should be compressed; there are a few resource types that are
// always compressed, and some types that are compressed if they're over a certain size
EResType Type = pEntry->ResourceType();
bool ShouldAlwaysCompress = (Type == eTexture || Type == eModel || Type == eSkin ||
Type == eAnimSet || Type == eAnimation || Type == eFont);
bool ShouldCompressConditional = !ShouldAlwaysCompress &&
(Type == eParticle || Type == eParticleElectric || Type == eParticleSwoosh ||
Type == eParticleWeapon || Type == eParticleDecal || Type == eParticleCollisionResponse);
bool ShouldCompress = ShouldAlwaysCompress || (ShouldCompressConditional && ResourceSize >= 0x400);
// Write resource data to pak
if (!ShouldCompress)
{
Pak.WriteBytes(ResourceData.data(), ResourceSize);
rTocInfo.Compressed = false;
}
else
{
u32 CompressedSize;
std::vector<u8> CompressedData(ResourceData.size() * 2);
bool Success = CompressionUtil::CompressZlib(ResourceData.data(), ResourceData.size(), CompressedData.data(), CompressedData.size(), CompressedSize);
// Make sure that the compressed data is actually smaller, accounting for padding + uncompressed size value
if (Success)
{
u32 PaddedUncompressedSize = (ResourceSize + 0x1F) & ~0x1F;
u32 PaddedCompressedSize = (CompressedSize + 4 + 0x1F) & ~0x1F;
Success = (PaddedCompressedSize < PaddedUncompressedSize);
}
// Write file to pak
if (Success)
{
Pak.WriteLong(ResourceSize);
Pak.WriteBytes(CompressedData.data(), CompressedSize);
}
else
Pak.WriteBytes(ResourceData.data(), ResourceSize);
rTocInfo.Compressed = Success;
}
Pak.WriteToBoundary(32, 0xFF);
rTocInfo.Size = Pak.Tell() - rTocInfo.Offset;
}
// Write table of contents for real
Pak.Seek(TocOffset, SEEK_SET);
for (u32 iRes = 0; iRes < AssetList.size(); iRes++)
{
const SResourceTocInfo& rkTocInfo = ResourceTocData[iRes];
CResourceEntry *pEntry = rkTocInfo.pEntry;
Pak.WriteLong( rkTocInfo.Compressed ? 1 : 0 );
pEntry->CookedExtension().Write(Pak);
pEntry->ID().Write(Pak);
Pak.WriteLong(rkTocInfo.Size);
Pak.WriteLong(rkTocInfo.Offset);
}
Log::Write("Finished writing " + PakPath.ToUTF8());
}
void CPackage::CompareOriginalAssetList(const std::list<CAssetID>& rkNewList)
{
// Debug - take the newly generated rkNewList and compare it with the asset list
// from the original pak, and print info about any extra or missing resources
// Build a set out of the generated list
std::set<CAssetID> NewListSet;
for (auto Iter = rkNewList.begin(); Iter != rkNewList.end(); Iter++)
NewListSet.insert(*Iter);
// Read the original pak
TWideString CookedPath = CookedPackagePath(false);
CFileInStream Pak(CookedPath.ToUTF8().ToStdString(), IOUtil::eBigEndian);
if (!Pak.IsValid())
{
Log::Error("Failed to compare to original asset list; couldn't open the original pak");
return;
}
// Skip past header + named resources
u32 PakVersion = Pak.ReadLong();
ASSERT(PakVersion == 0x00030005);
Pak.Seek(0x4, SEEK_CUR);
u32 NumNamedResources = Pak.ReadLong();
for (u32 iName = 0; iName < NumNamedResources; iName++)
{
Pak.Seek(0x8, SEEK_CUR);
u32 NameLen = Pak.ReadLong();
Pak.Seek(NameLen, SEEK_CUR);
}
// Build a set out of the original pak resource list
u32 NumResources = Pak.ReadLong();
std::set<CAssetID> OldListSet;
for (u32 iRes = 0; iRes < NumResources; iRes++)
{
Pak.Seek(0x8, SEEK_CUR);
OldListSet.insert( CAssetID(Pak, e32Bit) );
Pak.Seek(0x8, SEEK_CUR);
}
// Check for missing resources in the new list
for (auto Iter = OldListSet.begin(); Iter != OldListSet.end(); Iter++)
{
CAssetID ID = *Iter;
if (NewListSet.find(ID) == NewListSet.end())
{
CResourceEntry *pEntry = gpResourceStore->FindEntry(ID);
TString Extension = (pEntry ? "." + GetResourceCookedExtension(pEntry->ResourceType(), pEntry->Game()) : "");
Log::Error("Missing resource: " + ID.ToString() + Extension);
}
}
// Check for extra resources in the new list
for (auto Iter = NewListSet.begin(); Iter != NewListSet.end(); Iter++)
{
CAssetID ID = *Iter;
if (OldListSet.find(ID) == OldListSet.end())
{
CResourceEntry *pEntry = gpResourceStore->FindEntry(ID);
TString Extension = (pEntry ? "." + GetResourceCookedExtension(pEntry->ResourceType(), pEntry->Game()) : "");
Log::Error("Extra resource: " + ID.ToString() + Extension);
}
}
}
TWideString CPackage::DefinitionPath(bool Relative) const
{
return mpProject->PackagesDir(Relative) + mPakPath + mPakName.ToUTF16() + L".pkd";

View File

@@ -60,6 +60,9 @@ public:
void Load();
void Save();
void Cook();
void CompareOriginalAssetList(const std::list<CAssetID>& rkNewList);
TWideString DefinitionPath(bool Relative) const;
TWideString CookedPackagePath(bool Relative) const;
@@ -70,6 +73,7 @@ public:
// Accessors
inline TString Name() const { return mPakName; }
inline TWideString Path() const { return mPakPath; }
inline CGameProject* Project() const { return mpProject; }
inline u32 NumCollections() const { return mCollections.size(); }
inline CResourceCollection* CollectionByIndex(u32 Idx) const { return mCollections[Idx]; }

View File

@@ -54,6 +54,7 @@ bool CResourceEntry::LoadCacheData()
// Dependency Tree
u32 DepsTreeSize = File.ReadLong();
u32 DepsTreeStart = File.Tell();
if (mpDependencies)
{
@@ -63,8 +64,12 @@ bool CResourceEntry::LoadCacheData()
if (DepsTreeSize > 0)
{
mpDependencies = new CDependencyTree(mID);
if (mType == eArea) mpDependencies = new CAreaDependencyTree(mID);
else if (mType == eAnimSet) mpDependencies = new CAnimSetDependencyTree(mID);
else mpDependencies = new CDependencyTree(mID);
mpDependencies->Read(File, Game() <= eEchoes ? e32Bit : e64Bit);
ASSERT(File.Tell() - DepsTreeStart == DepsTreeSize);
}
return true;
@@ -166,6 +171,11 @@ TString CResourceEntry::CookedAssetPath(bool Relative) const
return ((IsTransient() || Relative) ? Path + Name : mpStore->ActiveProject()->CookedDir(false) + Path + Name);
}
CFourCC CResourceEntry::CookedExtension() const
{
return CFourCC( GetResourceCookedExtension(mType, mGame) );
}
bool CResourceEntry::IsInDirectory(CVirtualDirectory *pDir) const
{
CVirtualDirectory *pParentDir = mpDirectory;

View File

@@ -4,6 +4,7 @@
#include "CVirtualDirectory.h"
#include "Core/Resource/EResType.h"
#include <Common/CAssetID.h>
#include <Common/CFourCC.h>
#include <Common/Flags.h>
#include <Common/types.h>
@@ -52,6 +53,7 @@ public:
bool HasCookedVersion() const;
TString RawAssetPath(bool Relative = false) const;
TString CookedAssetPath(bool Relative = false) const;
CFourCC CookedExtension() const;
bool IsInDirectory(CVirtualDirectory *pDir) const;
u64 Size() const;
bool NeedsRecook() const;

View File

@@ -0,0 +1,264 @@
#ifndef DEPENDENCYLISTBUILDERS
#define DEPENDENCYLISTBUILDERS
#include "CDependencyTree.h"
#include "CPackage.h"
#include "CResourceEntry.h"
#include "Core/Resource/CDependencyGroup.h"
#include "Core/Resource/CWorld.h"
// ************ CPackageDependencyListBuilder ************
class CPackageDependencyListBuilder
{
CPackage *mpPackage;
TResPtr<CWorld> mpWorld;
std::set<CAssetID> mPackageUsedAssets;
std::set<CAssetID> mAreaUsedAssets;
std::list<CAssetID> mScanIDs;
bool mEnableDuplicates;
bool mCurrentAreaHasDuplicates;
bool mAddingScans;
public:
CPackageDependencyListBuilder(CPackage *pPackage)
: mpPackage(pPackage)
, mCurrentAreaHasDuplicates(false)
, mAddingScans(false)
{}
virtual void BuildDependencyList(bool AllowDuplicates, std::list<CAssetID>& rOut)
{
mEnableDuplicates = AllowDuplicates;
for (u32 iCol = 0; iCol < mpPackage->NumCollections(); iCol++)
{
CResourceCollection *pCollection = mpPackage->CollectionByIndex(iCol);
for (u32 iRes = 0; iRes < pCollection->NumResources(); iRes++)
{
const SNamedResource& rkRes = pCollection->ResourceByIndex(iRes);
CResourceEntry *pEntry = gpResourceStore->FindEntry(rkRes.ID);
if (pEntry)
{
if (rkRes.Name.EndsWith("NODEPEND"))
rOut.push_back(rkRes.ID);
else
{
mScanIDs.clear();
mAddingScans = false;
if (rkRes.Type == "MLVL")
{
CResourceEntry *pWorldEntry = gpResourceStore->FindEntry(rkRes.ID);
ASSERT(pWorldEntry);
mpWorld = (CWorld*) pWorldEntry->Load();
ASSERT(mpWorld);
}
EvaluateDependencies(pEntry, rOut);
mAddingScans = true;
for (auto Iter = mScanIDs.begin(); Iter != mScanIDs.end(); Iter++)
AddDependency(pEntry, *Iter, rOut);
}
}
}
}
}
inline void AddDependency(CResourceEntry *pCurEntry, const CAssetID& rkID, std::list<CAssetID>& rOut)
{
if (pCurEntry->ResourceType() == eDependencyGroup) return;
CResourceEntry *pEntry = gpResourceStore->FindEntry(rkID);
EResType Type = (pEntry ? pEntry->ResourceType() : eResource);
// Defer scans to the end of the list. This is to accomodate the way the game loads SCANs; they are
// loaded separately from everything else after the area itself is done loading.
if (Type == eScan && !mAddingScans)
{
mScanIDs.push_back(rkID);
return;
}
// Make sure entry exists + is valid
if (pEntry && Type != eMidi && Type != eAudioGroupSet && Type != eWorld && (Type != eArea || pCurEntry->ResourceType() == eWorld))
{
if ( ( mCurrentAreaHasDuplicates && mAreaUsedAssets.find(rkID) == mAreaUsedAssets.end()) ||
(!mCurrentAreaHasDuplicates && mPackageUsedAssets.find(rkID) == mPackageUsedAssets.end()) )
EvaluateDependencies(pEntry, rOut);
}
}
void EvaluateDependencies(CResourceEntry *pEntry, std::list<CAssetID>& rOut)
{
// Toggle duplicates
if (pEntry->ResourceType() == eArea && mEnableDuplicates)
{
mAreaUsedAssets.clear();
for (u32 iArea = 0; iArea < mpWorld->NumAreas(); iArea++)
{
if (mpWorld->AreaResourceID(iArea) == pEntry->ID())
{
mCurrentAreaHasDuplicates = mpWorld->DoesAreaAllowPakDuplicates(iArea);
break;
}
}
}
// Add dependencies
CDependencyTree *pTree = pEntry->Dependencies();
mPackageUsedAssets.insert(pTree->ID());
mAreaUsedAssets.insert(pTree->ID());
for (u32 iDep = 0; iDep < pTree->NumDependencies(); iDep++)
{
CAssetID ID = pTree->DependencyByIndex(iDep);
AddDependency(pEntry, ID, rOut);
}
// Add area script dependencies
if (pEntry->ResourceType() == eArea)
{
CAreaDependencyTree *pAreaTree = static_cast<CAreaDependencyTree*>(pTree);
for (u32 iInst = 0; iInst < pAreaTree->NumScriptInstances(); iInst++)
{
CScriptInstanceDependencyTree *pInstTree = pAreaTree->ScriptInstanceByIndex(iInst);
for (u32 iDep = 0; iDep < pInstTree->NumDependencies(); iDep++)
{
CAssetID ID = pInstTree->DependencyByIndex(iDep);
AddDependency(pEntry, ID, rOut);
}
}
}
rOut.push_back(pTree->ID());
}
};
// ************ CAreaDependencyListBuilder ************
class CAreaDependencyListBuilder
{
CResourceEntry *mpAreaEntry;
std::set<CAssetID> mBaseUsedAssets;
std::set<CAssetID> mLayerUsedAssets;
bool mIsPlayerActor;
public:
CAreaDependencyListBuilder(CResourceEntry *pAreaEntry)
: mpAreaEntry(pAreaEntry) {}
virtual void BuildDependencyList(std::list<CAssetID>& rAssetsOut, std::list<u32>& rLayerOffsetsOut)
{
ASSERT(mpAreaEntry->ResourceType() == eArea);
CAreaDependencyTree *pTree = static_cast<CAreaDependencyTree*>(mpAreaEntry->Dependencies());
// Fill area base used assets set (don't actually add to list yet)
for (u32 iDep = 0; iDep < pTree->NumDependencies(); iDep++)
mBaseUsedAssets.insert(pTree->DependencyByIndex(iDep));
// Get dependencies of each layer
for (u32 iLyr = 0; iLyr < pTree->NumScriptLayers(); iLyr++)
{
mLayerUsedAssets.clear();
rLayerOffsetsOut.push_back(rAssetsOut.size());
bool IsLastLayer = (iLyr == pTree->NumScriptLayers() - 1);
u32 StartIndex = pTree->ScriptLayerOffset(iLyr);
u32 EndIndex = (IsLastLayer ? pTree->NumScriptInstances() : pTree->ScriptLayerOffset(iLyr + 1));
for (u32 iInst = StartIndex; iInst < EndIndex; iInst++)
{
CScriptInstanceDependencyTree *pInstTree = pTree->ScriptInstanceByIndex(iInst);
mIsPlayerActor = (pInstTree->ObjectType() == 0x4C);
for (u32 iDep = 0; iDep < pInstTree->NumDependencies(); iDep++)
{
CAssetID ID = pInstTree->DependencyByIndex(iDep);
AddDependency(mpAreaEntry, ID, rAssetsOut);
}
}
}
// Add base dependencies
mBaseUsedAssets.clear();
mLayerUsedAssets.clear();
rLayerOffsetsOut.push_back(rAssetsOut.size());
for (u32 iDep = 0; iDep < pTree->NumDependencies(); iDep++)
{
CAssetID ID = pTree->DependencyByIndex(iDep);
AddDependency(mpAreaEntry, ID, rAssetsOut);
}
}
void AddDependency(CResourceEntry *pCurEntry, const CAssetID& rkID, std::list<CAssetID>& rOut)
{
if (pCurEntry->ResourceType() == eDependencyGroup) return;
CResourceEntry *pEntry = gpResourceStore->FindEntry(rkID);
EResType Type = (pEntry ? pEntry->ResourceType() : eResource);
// Make sure entry exists + is valid
if (pEntry && Type != eMidi && Type != eAudioGroupSet && Type != eScan && Type != eWorld && Type != eArea)
{
if ( mBaseUsedAssets.find(rkID) == mBaseUsedAssets.end() && mLayerUsedAssets.find(rkID) == mLayerUsedAssets.end())
{
if (mIsPlayerActor && pEntry->ResourceType() == eAnimSet)
EvaluatePlayerActorAnimSet(pEntry, rOut);
else
EvaluateDependencies(pEntry, rOut);
}
}
}
void EvaluateDependencies(CResourceEntry *pEntry, std::list<CAssetID>& rOut)
{
CDependencyTree *pTree = pEntry->Dependencies();
mLayerUsedAssets.insert(pTree->ID());
for (u32 iDep = 0; iDep < pTree->NumDependencies(); iDep++)
{
CAssetID ID = pTree->DependencyByIndex(iDep);
AddDependency(pEntry, ID, rOut);
}
rOut.push_back(pTree->ID());
}
void EvaluatePlayerActorAnimSet(CResourceEntry *pEntry, std::list<CAssetID>& rOut)
{
// For PlayerActor animsets we want to include only the empty suit (char 5) in the dependency list. This is to
// accomodate the dynamic loading the game does for PlayerActors to avoid having assets for suits the player
// doesn't have in memory. We want common assets (animations, etc) in the list but not per-character assets.
ASSERT(pEntry->ResourceType() == eAnimSet);
CAnimSetDependencyTree *pTree = static_cast<CAnimSetDependencyTree*>(pEntry->Dependencies());
mLayerUsedAssets.insert(pTree->ID());
// Add empty suit dependencies
ASSERT(pTree->NumCharacters() >= 7);
u32 StartIdx = pTree->CharacterOffset(5);
u32 EndIdx = pTree->CharacterOffset(6);
for (u32 iDep = StartIdx; iDep < EndIdx; iDep++)
{
CAssetID ID = pTree->DependencyByIndex(iDep);
AddDependency(pEntry, ID, rOut);
}
// Add common dependencies
for (u32 iDep = 0; iDep < pTree->CharacterOffset(0); iDep++)
{
CAssetID ID = pTree->DependencyByIndex(iDep);
AddDependency(pEntry, ID, rOut);
}
rOut.push_back(pTree->ID());
}
};
#endif // DEPENDENCYLISTBUILDERS