mirror of
https://github.com/AxioDL/PrimeWorldEditor.git
synced 2025-12-17 08:57:09 +00:00
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:
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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]; }
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
264
src/Core/GameProject/DependencyListBuilders.h
Normal file
264
src/Core/GameProject/DependencyListBuilders.h
Normal 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
|
||||
|
||||
Reference in New Issue
Block a user