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

@ -38,6 +38,14 @@ CAssetID::CAssetID(const char* pkID)
*this = CAssetID::FromString(pkID);
}
void CAssetID::Write(IOutputStream& rOutput) const
{
if (mLength == e32Bit)
rOutput.WriteLong(ToLong());
else
rOutput.WriteLongLong(ToLongLong());
}
CAssetID::CAssetID(IInputStream& rInput, EIDLength Length)
: mLength(Length)
{

View File

@ -23,6 +23,7 @@ public:
CAssetID(u64 ID, EIDLength Length);
CAssetID(const char* pkID);
CAssetID(IInputStream& rInput, EIDLength Length);
void Write(IOutputStream& rOutput) const;
TString ToString() const;
bool IsValid() const;

View File

@ -35,7 +35,7 @@ public:
if (rInput.GetEndianness() == IOUtil::eLittleEndian) Reverse();
}
inline void Write(IOutputStream& rOutput)
inline void Write(IOutputStream& rOutput) const
{
u32 Val = mFourCC;
if (rOutput.GetEndianness() == IOUtil::eLittleEndian) IOUtil::SwapBytes(Val);

View File

@ -203,7 +203,8 @@ HEADERS += \
Resource/ParticleParameters.h \
Resource/Factory/CUnsupportedParticleLoader.h \
Resource/Resources.h \
Resource/Factory/CResourceFactory.h
Resource/Factory/CResourceFactory.h \
GameProject/DependencyListBuilders.h
# Source Files
SOURCES += \

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,10 +540,28 @@ 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();
}
}
}
}
void CGameExporter::ExportResource(SResourceInstance& rRes)
{

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

View File

@ -36,14 +36,11 @@ CDependencyTree* CGameArea::BuildDependencyTree() const
// Base dependencies
CAreaDependencyTree *pTree = new CAreaDependencyTree(ID());
for (u32 iMat = 0; iMat < mpMaterialSet->NumMaterials(); iMat++)
{
CMaterial *pMat = mpMaterialSet->MaterialByIndex(iMat);
pTree->AddDependency(pMat->IndTexture());
std::set<CAssetID> MatTextures;
mpMaterialSet->GetUsedTextureIDs(MatTextures);
for (u32 iPass = 0; iPass < pMat->PassCount(); iPass++)
pTree->AddDependency(pMat->Pass(iPass)->Texture());
}
for (auto Iter = MatTextures.begin(); Iter != MatTextures.end(); Iter++)
pTree->AddDependency(*Iter);
pTree->AddDependency(mPathID);
pTree->AddDependency(mPortalAreaID);

View File

@ -58,12 +58,23 @@ public:
CDependencyTree* BuildDependencyTree() const
{
CAnimSetDependencyTree *pTree = new CAnimSetDependencyTree(ID());
std::set<CAssetID> BaseUsedSet;
// Base dependencies
for (u32 iAnim = 0; iAnim < mAnims.size(); iAnim++)
pTree->AddDependency(mAnims[iAnim].pAnim);
{
CAnimation *pAnim = mAnims[iAnim].pAnim;
if (pAnim)
{
pTree->AddDependency(mAnims[iAnim].pAnim);
BaseUsedSet.insert(pAnim->ID());
}
}
// Character dependencies
for (u32 iNode = 0; iNode < mCharacters.size(); iNode++)
pTree->AddCharacter(&mCharacters[iNode]);
pTree->AddCharacter(&mCharacters[iNode], BaseUsedSet);
return pTree;
}

View File

@ -59,6 +59,21 @@ public:
return -1;
}
void GetUsedTextureIDs(std::set<CAssetID>& rOut)
{
for (u32 iMat = 0; iMat < mMaterials.size(); iMat++)
{
CMaterial *pMat = mMaterials[iMat];
if (pMat->IndTexture()) rOut.insert(pMat->IndTexture()->ID());
for (u32 iPass = 0; iPass < pMat->PassCount(); iPass++)
{
CTexture *pTex = pMat->Pass(iPass)->Texture();
if (pTex) rOut.insert(pTex->ID());
}
}
}
};
#endif // CMATERIALSET_H

View File

@ -24,16 +24,16 @@ public:
private:
EGame mVersion;
TResPtr<CResource> mpFrame;
CAssetID mFrameID;
TResPtr<CStringTable> mpStringTable;
bool mIsSlow;
bool mIsImportant;
ELogbookCategory mCategory;
CAssetID mScanImageTextures[4];
public:
CScan(CResourceEntry *pEntry = 0)
: CResource(pEntry)
, mpFrame(nullptr)
, mpStringTable(nullptr)
, mIsSlow(false)
, mIsImportant(false)
@ -46,8 +46,12 @@ public:
Log::Warning("CScan::BuildDependencyTree not handling Echoes/Corruption dependencies");
CDependencyTree *pTree = new CDependencyTree(ID());
pTree->AddDependency(mpFrame);
pTree->AddDependency(mFrameID);
pTree->AddDependency(mpStringTable);
for (u32 iImg = 0; iImg < 4; iImg++)
pTree->AddDependency(mScanImageTextures[iImg]);
return pTree;
}

View File

@ -44,7 +44,7 @@ public:
CDependencyTree* BuildDependencyTree() const
{
// The only dependencies STRGs have is they can reference FONTs with the &font=; formatting tag
// STRGs can reference FONTs with the &font=; formatting tag and TXTRs with the &image=; tag
CDependencyTree *pTree = new CDependencyTree(ID());
EIDLength IDLength = (Game() <= eEchoes ? e32Bit : e64Bit);
@ -54,14 +54,69 @@ public:
for (u32 iStr = 0; iStr < rkTable.Strings.size(); iStr++)
{
static const TWideString skTag = L"&font=";
const TWideString& rkStr = rkTable.Strings[iStr];
for (u32 FontIdx = rkStr.IndexOfPhrase(*skTag); FontIdx != -1; FontIdx = rkStr.IndexOfPhrase(*skTag, FontIdx + 1))
for (u32 TagIdx = rkStr.IndexOf(L'&'); TagIdx != -1; TagIdx = rkStr.IndexOf(L'&', TagIdx + 1))
{
u32 IDStart = FontIdx + skTag.Size();
TWideString StrFontID = rkStr.SubString(IDStart, IDLength * 2);
pTree->AddDependency( CAssetID::FromString(StrFontID) );
// Check for double ampersand (escape character in DKCR, not sure about other games)
if (rkStr.At(TagIdx + 1) == L'&')
{
TagIdx++;
continue;
}
// Get tag name and parameters
u32 NameEnd = rkStr.IndexOf(L'=', TagIdx);
u32 TagEnd = rkStr.IndexOf(L';', TagIdx);
if (NameEnd == -1 || TagEnd == -1) continue;
TWideString TagName = rkStr.SubString(TagIdx + 1, NameEnd - TagIdx - 1);
TWideString ParamString = rkStr.SubString(NameEnd + 1, TagEnd - NameEnd - 1);
// Font
if (TagName == L"font")
{
ASSERT(ParamString.Size() == IDLength * 2);
pTree->AddDependency( CAssetID::FromString(ParamString) );
}
// Image
else if (TagName == L"image")
{
// Determine which params are textures based on image type
TWideStringList Params = ParamString.Split(L",");
TWideString ImageType = Params.front();
u32 TexturesStart = -1;
if (ImageType == L"A")
TexturesStart = 2;
else if (ImageType == L"SI")
TexturesStart = 3;
else if (ImageType == L"SA")
TexturesStart = 4;
else
{
Log::Error("Unrecognized image type: " + ImageType.ToUTF8());
DEBUG_BREAK;
continue;
}
// Load texture IDs
TWideStringList::iterator Iter = Params.begin();
for (u32 iParam = 0; iParam < Params.size(); iParam++, Iter++)
{
if (iParam >= TexturesStart)
{
TWideString Param = *Iter;
ASSERT(Param.Size() == IDLength * 2);
pTree->AddDependency( CAssetID::FromString(Param) );
}
}
}
}
}
}

View File

@ -92,6 +92,7 @@ void Serialize(IArchive& rArc, CWorld::SArea& rArea)
<< SERIAL("BoundingBox", rArea.AetherBox)
<< SERIAL("AreaMREA", rArea.AreaResID)
<< SERIAL_HEX("AreaID", rArea.AreaID)
<< SERIAL("AllowPakDuplicates", rArea.AllowPakDuplicates)
<< SERIAL_CONTAINER("AttachedAreas", rArea.AttachedAreaIDs, "AreaIndex")
<< SERIAL_CONTAINER("Dependencies", rArea.Dependencies, "Dependency")
<< SERIAL_CONTAINER("RelModules", rArea.RelFilenames, "Module")

View File

@ -11,6 +11,7 @@ class CWorld : public CResource
{
DECLARE_RESOURCE_TYPE(eWorld)
friend class CWorldLoader;
friend class CWorldCooker;
// Instances of CResource pointers are placeholders for unimplemented resource types (eg CMapWorld)
EGame mWorldVersion;
@ -48,6 +49,7 @@ class CWorld : public CResource
CAABox AetherBox;
CAssetID AreaResID; // Loading every single area as a CResource would be a very bad idea
u64 AreaID;
bool AllowPakDuplicates;
std::vector<u16> AttachedAreaIDs;
std::vector<CAssetID> Dependencies;
@ -103,11 +105,14 @@ public:
inline CResource* MapWorld() const { return mpMapWorld; }
inline u32 NumAreas() const { return mAreas.size(); }
inline u64 AreaResourceID(u32 AreaIndex) const { return mAreas[AreaIndex].AreaResID.ToLongLong(); }
inline CAssetID AreaResourceID(u32 AreaIndex) const { return mAreas[AreaIndex].AreaResID; }
inline u32 AreaAttachedCount(u32 AreaIndex) const { return mAreas[AreaIndex].AttachedAreaIDs.size(); }
inline u32 AreaAttachedID(u32 AreaIndex, u32 AttachedIndex) const { return mAreas[AreaIndex].AttachedAreaIDs[AttachedIndex]; }
inline TString AreaInternalName(u32 AreaIndex) const { return mAreas[AreaIndex].InternalName; }
inline CStringTable* AreaName(u32 AreaIndex) const { return mAreas[AreaIndex].pAreaName; }
inline bool DoesAreaAllowPakDuplicates(u32 AreaIndex) const { return mAreas[AreaIndex].AllowPakDuplicates; }
inline void SetAreaAllowsPakDuplicates(u32 AreaIndex, bool Allow) { mAreas[AreaIndex].AllowPakDuplicates = Allow; }
};
#endif // CWORLD_H

View File

@ -31,6 +31,4 @@ void CPoiToWorldCooker::WriteEGMC(CPoiToWorld *pPoiToWorld, IOutputStream& rOut)
rOut.WriteLong(Mappings[iMap].MeshID);
rOut.WriteLong(Mappings[iMap].PoiID);
}
rOut.WriteToBoundary(32, -1);
}

View File

@ -1,9 +1,162 @@
#include "CWorldCooker.h"
#include "Core/GameProject/DependencyListBuilders.h"
CWorldCooker::CWorldCooker()
{
}
// ************ STATIC ************
bool CWorldCooker::CookMLVL(CWorld *pWorld, IOutputStream& rMLVL)
{
ASSERT(rMLVL.IsValid());
// MLVL Header
rMLVL.WriteLong(0xDEAFBABE);
rMLVL.WriteLong( GetMLVLVersion(pWorld->Game()) );
CAssetID WorldNameID = pWorld->mpWorldName ? pWorld->mpWorldName->ID() : CAssetID::skInvalidID32;
CAssetID SaveWorldID = pWorld->mpSaveWorld ? pWorld->mpSaveWorld->ID() : CAssetID::skInvalidID32;
CAssetID DefaultSkyID = pWorld->mpDefaultSkybox ? pWorld->mpDefaultSkybox->ID() : CAssetID::skInvalidID32;
WorldNameID.Write(rMLVL);
SaveWorldID.Write(rMLVL);
DefaultSkyID.Write(rMLVL);
// Memory Relays
rMLVL.WriteLong( pWorld->mMemoryRelays.size() );
for (u32 iMem = 0; iMem < pWorld->mMemoryRelays.size(); iMem++)
{
CWorld::SMemoryRelay& rRelay = pWorld->mMemoryRelays[iMem];
rMLVL.WriteLong(rRelay.InstanceID);
rMLVL.WriteLong(rRelay.TargetID);
rMLVL.WriteShort(rRelay.Message);
rMLVL.WriteByte(rRelay.Unknown);
}
// Areas
rMLVL.WriteLong(pWorld->mAreas.size());
rMLVL.WriteLong(1); // Unknown
for (u32 iArea = 0; iArea < pWorld->mAreas.size(); iArea++)
{
// Area Header
CWorld::SArea& rArea = pWorld->mAreas[iArea];
CResourceEntry *pAreaEntry = gpResourceStore->FindEntry(rArea.AreaResID);
ASSERT(pAreaEntry && pAreaEntry->ResourceType() == eArea);
CAssetID AreaNameID = rArea.pAreaName ? rArea.pAreaName->ID() : CAssetID::skInvalidID32;
AreaNameID.Write(rMLVL);
rArea.Transform.Write(rMLVL);
rArea.AetherBox.Write(rMLVL);
rArea.AreaResID.Write(rMLVL);
rMLVL.WriteLong( (u32) rArea.AreaID );
// Attached Areas
rMLVL.WriteLong( rArea.AttachedAreaIDs.size() );
for (u32 iAttach = 0; iAttach < rArea.AttachedAreaIDs.size(); iAttach++)
rMLVL.WriteShort(rArea.AttachedAreaIDs[iAttach]);
// Dependencies
std::list<CAssetID> Dependencies;
std::list<u32> LayerDependsOffsets;
CAreaDependencyListBuilder Builder(pAreaEntry);
Builder.BuildDependencyList(Dependencies, LayerDependsOffsets);
rMLVL.WriteLong(0);
rMLVL.WriteLong( Dependencies.size() );
for (auto Iter = Dependencies.begin(); Iter != Dependencies.end(); Iter++)
{
CAssetID ID = *Iter;
CResourceEntry *pEntry = gpResourceStore->FindEntry(ID);
ID.Write(rMLVL);
pEntry->CookedExtension().Write(rMLVL);
}
rMLVL.WriteLong(LayerDependsOffsets.size());
for (auto Iter = LayerDependsOffsets.begin(); Iter != LayerDependsOffsets.end(); Iter++)
rMLVL.WriteLong(*Iter);
// Docks
rMLVL.WriteLong( rArea.Docks.size() );
for (u32 iDock = 0; iDock < rArea.Docks.size(); iDock++)
{
CWorld::SArea::SDock& rDock = rArea.Docks[iDock];
rMLVL.WriteLong( rDock.ConnectingDocks.size() );
for (u32 iCon = 0; iCon < rDock.ConnectingDocks.size(); iCon++)
{
CWorld::SArea::SDock::SConnectingDock& rConDock = rDock.ConnectingDocks[iCon];
rMLVL.WriteLong(rConDock.AreaIndex);
rMLVL.WriteLong(rConDock.DockIndex);
}
rMLVL.WriteLong( rDock.DockCoordinates.size() );
for (u32 iCoord = 0; iCoord < rDock.DockCoordinates.size(); iCoord++)
rDock.DockCoordinates[iCoord].Write(rMLVL);
}
}
CAssetID MapWorldID = pWorld->mpMapWorld ? pWorld->mpMapWorld->ID() : CAssetID::skInvalidID32;
MapWorldID.Write(rMLVL);
rMLVL.WriteByte(0);
rMLVL.WriteLong(0);
// Audio Groups
rMLVL.WriteLong(pWorld->mAudioGrps.size());
for (u32 iGrp = 0; iGrp < pWorld->mAudioGrps.size(); iGrp++)
{
CWorld::SAudioGrp& rAudioGroup = pWorld->mAudioGrps[iGrp];
rMLVL.WriteLong(rAudioGroup.Unknown);
rAudioGroup.ResID.Write(rMLVL);
}
rMLVL.WriteByte(0);
// Layers
rMLVL.WriteLong(pWorld->mAreas.size());
std::vector<TString> LayerNames;
std::vector<u32> LayerNameOffsets;
for (u32 iArea = 0; iArea < pWorld->mAreas.size(); iArea++)
{
CWorld::SArea& rArea = pWorld->mAreas[iArea];
LayerNameOffsets.push_back(LayerNames.size());
rMLVL.WriteLong(rArea.Layers.size());
u64 LayerActiveFlags = -1;
for (u32 iLyr = 0; iLyr < rArea.Layers.size(); iLyr++)
{
CWorld::SArea::SLayer& rLayer = rArea.Layers[iLyr];
if (!rLayer.EnabledByDefault)
LayerActiveFlags &= ~(1 << iLyr);
LayerNames.push_back(rLayer.LayerName);
}
rMLVL.WriteLongLong(LayerActiveFlags);
}
rMLVL.WriteLong(LayerNames.size());
for (u32 iLyr = 0; iLyr < LayerNames.size(); iLyr++)
rMLVL.WriteString(LayerNames[iLyr].ToStdString());
rMLVL.WriteLong(LayerNameOffsets.size());
for (u32 iOff = 0; iOff < LayerNameOffsets.size(); iOff++)
rMLVL.WriteLong(LayerNameOffsets[iOff]);
return true;
}
u32 CWorldCooker::GetMLVLVersion(EGame Version)
{
switch (Version)

View File

@ -1,6 +1,7 @@
#ifndef CWORLDCOOKER_H
#define CWORLDCOOKER_H
#include "Core/Resource/CWorld.h"
#include "Core/Resource/EGame.h"
#include <Common/types.h>
@ -8,6 +9,7 @@ class CWorldCooker
{
CWorldCooker();
public:
static bool CookMLVL(CWorld *pWorld, IOutputStream& rOut);
static u32 GetMLVLVersion(EGame Version);
};

View File

@ -681,6 +681,7 @@ CGameArea* CAreaLoader::LoadMREA(IInputStream& MREA, CResourceEntry *pEntry)
Loader.ReadSCLYPrime();
Loader.ReadCollision();
Loader.ReadLightsPrime();
Loader.ReadPATH();
break;
case eEchoesDemo:
Loader.ReadHeaderEchoes();
@ -688,6 +689,8 @@ CGameArea* CAreaLoader::LoadMREA(IInputStream& MREA, CResourceEntry *pEntry)
Loader.ReadSCLYPrime();
Loader.ReadCollision();
Loader.ReadLightsPrime();
Loader.ReadPATH();
Loader.ReadPTLA();
Loader.ReadEGMC();
break;
case eEchoes:
@ -696,6 +699,8 @@ CGameArea* CAreaLoader::LoadMREA(IInputStream& MREA, CResourceEntry *pEntry)
Loader.ReadSCLYEchoes();
Loader.ReadCollision();
Loader.ReadLightsPrime();
Loader.ReadPATH();
Loader.ReadPTLA();
Loader.ReadEGMC();
break;
case eCorruptionProto:
@ -704,6 +709,8 @@ CGameArea* CAreaLoader::LoadMREA(IInputStream& MREA, CResourceEntry *pEntry)
Loader.ReadSCLYEchoes();
Loader.ReadCollision();
Loader.ReadLightsCorruption();
Loader.ReadPATH();
Loader.ReadPTLA();
Loader.ReadEGMC();
break;
case eCorruption:
@ -715,6 +722,8 @@ CGameArea* CAreaLoader::LoadMREA(IInputStream& MREA, CResourceEntry *pEntry)
if (Loader.mVersion == eCorruption)
{
Loader.ReadLightsCorruption();
Loader.ReadPATH();
Loader.ReadPTLA();
Loader.ReadEGMC();
}
break;

View File

@ -5,8 +5,8 @@ EGame CDependencyGroupLoader::VersionTest(IInputStream& rDGRP, u32 DepCount)
{
// Only difference between versions is asset ID length. Just check for EOF with 32-bit ID length.
u32 Start = rDGRP.Tell();
rDGRP.Seek(DepCount * 4, SEEK_CUR);
u32 Remaining = rDGRP.Size() - Start;
rDGRP.Seek(DepCount * 8, SEEK_CUR);
u32 Remaining = rDGRP.Size() - rDGRP.Tell();
EGame Game = ePrimeDemo;
@ -14,7 +14,9 @@ EGame CDependencyGroupLoader::VersionTest(IInputStream& rDGRP, u32 DepCount)
{
for (u32 iRem = 0; iRem < Remaining; iRem++)
{
if (rDGRP.ReadByte() != 0xFF)
u8 Byte = rDGRP.ReadByte();
if (Byte != 0xFF)
{
Game = eCorruptionProto;
break;

View File

@ -120,7 +120,7 @@ CMaterial* CMaterialLoader::ReadPrimeMaterial()
if (pMat->mOptions & CMaterial::eIndStage)
{
u32 IndTexIndex = mpFile->ReadLong();
pMat->mpIndirectTexture = mTextures[IndTexIndex];
pMat->mpIndirectTexture = mTextures[TextureIndices[IndTexIndex]];
}
// Color channels
@ -162,7 +162,7 @@ CMaterial* CMaterialLoader::ReadPrimeMaterial()
u8 TexSel = mpFile->ReadByte();
if ((TexSel == 0xFF) || (TexSel >= mTextures.size()))
if ((TexSel == 0xFF) || (TexSel >= TextureIndices.size()))
pPass->mpTexture = nullptr;
else
pPass->mpTexture = mTextures[TextureIndices[TexSel]];

View File

@ -9,11 +9,18 @@ CScanLoader::CScanLoader()
CScan* CScanLoader::LoadScanMP1(IInputStream& rSCAN)
{
// Basic support at the moment - don't read animation/scan image data
rSCAN.Seek(0x4, SEEK_CUR); // Skip FRME ID
mpScan->mFrameID = CAssetID(rSCAN, e32Bit);
mpScan->mpStringTable = gpResourceStore->LoadResource(rSCAN.ReadLong(), "STRG");
mpScan->mIsSlow = (rSCAN.ReadLong() != 0);
mpScan->mCategory = (CScan::ELogbookCategory) rSCAN.ReadLong();
mpScan->mIsImportant = (rSCAN.ReadByte() == 1);
for (u32 iImg = 0; iImg < 4; iImg++)
{
mpScan->mScanImageTextures[iImg] = CAssetID(rSCAN, e32Bit);
rSCAN.Seek(0x18, SEEK_CUR);
}
mpScan->mVersion = ePrime;
return mpScan;
}

View File

@ -33,23 +33,16 @@ CModel::~CModel()
CDependencyTree* CModel::BuildDependencyTree() const
{
CDependencyTree *pTree = new CDependencyTree(ID());
std::set<CAssetID> TextureIDs;
for (u32 iSet = 0; iSet < mMaterialSets.size(); iSet++)
{
CMaterialSet *pSet = mMaterialSets[iSet];
pSet->GetUsedTextureIDs(TextureIDs);
}
for (u32 iMat = 0; iMat < pSet->NumMaterials(); iMat++)
{
CMaterial *pMat = pSet->MaterialByIndex(iMat);
pTree->AddDependency(pMat->IndTexture());
for (u32 iPass = 0; iPass < pMat->PassCount(); iPass++)
{
CMaterialPass *pPass = pMat->Pass(iPass);
pTree->AddDependency(pPass->Texture());
}
}
}
for (auto Iter = TextureIDs.begin(); Iter != TextureIDs.end(); Iter++)
pTree->AddDependency(*Iter);
return pTree;
}

View File

@ -20,6 +20,7 @@ CProjectOverviewDialog::CProjectOverviewDialog(QWidget *pParent)
connect(mpUI->LoadWorldButton, SIGNAL(clicked()), this, SLOT(LoadWorld()));
connect(mpUI->LaunchEditorButton, SIGNAL(clicked()), this, SLOT(LaunchEditor()));
connect(mpUI->ViewResourcesButton, SIGNAL(clicked()), this, SLOT(LaunchResourceBrowser()));
connect(mpUI->CookPackageButton, SIGNAL(clicked()), this, SLOT(CookPackage()));
}
CProjectOverviewDialog::~CProjectOverviewDialog()
@ -44,6 +45,7 @@ void CProjectOverviewDialog::OpenProject()
mpProject = pNewProj;
mpProject->SetActive();
SetupWorldsList();
SetupPackagesList();
}
else
@ -113,6 +115,19 @@ void CProjectOverviewDialog::SetupWorldsList()
mpUI->LoadWorldButton->setEnabled(!mWorldEntries.isEmpty());
}
void CProjectOverviewDialog::SetupPackagesList()
{
ASSERT(mpProject != nullptr && mpProject->IsActive());
mpUI->PackagesList->clear();
for (u32 iPkg = 0; iPkg < mpProject->NumPackages(); iPkg++)
{
CPackage *pPackage = mpProject->PackageByIndex(iPkg);
ASSERT(pPackage != nullptr);
mpUI->PackagesList->addItem(TO_QSTRING(pPackage->Name()));
}
}
void CProjectOverviewDialog::LoadWorld()
{
// Find world
@ -145,9 +160,6 @@ void CProjectOverviewDialog::LoadWorld()
void CProjectOverviewDialog::LaunchEditor()
{
CGameArea *pOldArea = mpWorldEditor->ActiveArea();
(void) pOldArea;
// Load area
u32 AreaIdx = mpUI->AreaComboBox->currentIndex();
CResourceEntry *pAreaEntry = mAreaEntries[AreaIdx];
@ -172,3 +184,10 @@ void CProjectOverviewDialog::LaunchResourceBrowser()
CResourceBrowser Browser(this);
Browser.exec();
}
void CProjectOverviewDialog::CookPackage()
{
u32 PackageIdx = mpUI->PackagesList->currentRow();
CPackage *pPackage = mpProject->PackageByIndex(PackageIdx);
pPackage->Cook();
}

View File

@ -31,8 +31,10 @@ public slots:
void LoadWorld();
void LaunchEditor();
void LaunchResourceBrowser();
void CookPackage();
void SetupWorldsList();
void SetupPackagesList();
};
#endif // CPROJECTOVERVIEWDIALOG_H

View File

@ -6,13 +6,15 @@
<rect>
<x>0</x>
<y>0</y>
<width>268</width>
<height>367</height>
<width>492</width>
<height>445</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
@ -39,6 +41,12 @@
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QGroupBox" name="WorldsGroupBox">
<property name="sizePolicy">
@ -100,6 +108,29 @@
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="PackagesGroupBox">
<property name="title">
<string>Packages</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QListWidget" name="PackagesList"/>
</item>
<item>
<widget class="QPushButton" name="CookPackageButton">
<property name="text">
<string>Cook Package</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>

View File

@ -144,14 +144,8 @@ void CStartWindow::FillAreaUI()
else
ui->AreaNameSTRGLineEdit->clear();
u64 MREA = mpWorld->AreaResourceID(mSelectedAreaIndex);
TString MREAStr;
if (MREA & 0xFFFFFFFF00000000)
MREAStr = TString::FromInt64(MREA, 16);
else
MREAStr = TString::FromInt32(MREA, 8);
ui->AreaMREALineEdit->setText(TO_QSTRING(MREAStr) + QString(".MREA"));
CAssetID MREA = mpWorld->AreaResourceID(mSelectedAreaIndex);
ui->AreaMREALineEdit->setText(TO_QSTRING(MREA.ToString()) + QString(".MREA"));
u32 NumAttachedAreas = mpWorld->AreaAttachedCount(mSelectedAreaIndex);
ui->AttachedAreasList->clear();

View File

@ -75,7 +75,7 @@ void IOutputStream::WriteWideString(const std::wstring& rkVal, unsigned long Cou
WriteShort(0);
}
void IOutputStream::WriteToBoundary(unsigned long Boundary, char Fill)
void IOutputStream::WriteToBoundary(unsigned long Boundary, unsigned char Fill)
{
long Num = Boundary - (Tell() % Boundary);
if (Num == Boundary) return;

View File

@ -22,7 +22,7 @@ public:
void WriteWideString(const std::wstring& rkVal);
void WriteWideString(const std::wstring& rkVal, unsigned long Count, bool Terminate = false);
void WriteToBoundary(unsigned long Boundary, char Fill);
void WriteToBoundary(unsigned long Boundary, unsigned char Fill);
void SetEndianness(IOUtil::EEndianness Endianness);
void SetDestString(const std::string& rkDest);
IOUtil::EEndianness GetEndianness() const;

View File

@ -11,6 +11,7 @@
<enumerator ID="0x00" name="None"/>
<enumerator ID="0x01" name="Snow"/>
<enumerator ID="0x02" name="Rain"/>
<enumerator ID="0x03" name="Bubbles"/>
</enumerators>
</enum>
<property ID="0x03" name="Initial Environmental Effect Density" type="float"/>

View File

@ -13,9 +13,9 @@
<struct ID="0x08" name="DamageInfo 1" template="Structs/DamageInfo.xml"/>
<property ID="0x09" name="Unknown 3" type="long"/>
<struct ID="0x0A" name="DamageInfo 2" template="Structs/DamageInfo.xml"/>
<property ID="0x0B" name="Unknown 4" type="long"/>
<property ID="0x0C" name="Unknown 5" type="long"/>
<property ID="0x0D" name="Unknown 6" type="long"/>
<property ID="0x0B" name="Particle 1" type="file" extensions="PART"/>
<property ID="0x0C" name="Particle 2" type="file" extensions="PART"/>
<property ID="0x0D" name="Model 1" type="file" extensions="CMDL"/>
<struct ID="0x0E" name="FlareDef 1" template="Structs/FlareDef.xml"/>
<struct ID="0x0F" name="FlareDef 2" template="Structs/FlareDef.xml"/>
<struct ID="0x10" name="FlareDef 3" template="Structs/FlareDef.xml"/>

View File

@ -27,7 +27,7 @@
<property ID="0x16" name="Unknown 17" type="color"/>
<property ID="0x17" name="Unknown 18" type="bool"/>
<property ID="0x18" name="Unknown 19" type="float"/>
<property ID="0x19" name="Unknown 20" type="long"/>
<property ID="0x19" name="Particle" type="file" extensions="PART"/>
<property ID="0x1A" name="Unknown 21" type="long"/>
<property ID="0x1B" name="Unknown 22" type="long"/>
<property ID="0x1C" name="Unknown 23" type="long"/>

View File

@ -8,8 +8,8 @@
<property ID="0x03" name="Scale" type="vector3f"/>
<struct ID="0x04" name="PatternedInfo" template="Structs/PatternedInfo.xml"/>
<struct ID="0x05" name="ActorParameters" template="Structs/ActorParameters.xml"/>
<property ID="0x06" name="Unknown 1" type="float"/>
<property ID="0x07" name="Unknown 2" type="float"/>
<property ID="0x06" name="Unknown" type="float"/>
<property ID="0x07" name="WPSC 1" type="file" extensions="WPSC"/>
<struct ID="0x08" name="DamageInfo" template="Structs/DamageInfo.xml"/>
<property ID="0x09" name="Particle 1" type="file" extensions="PART"/>
<property ID="0x0A" name="Particle 2" type="file" extensions="PART"/>

View File

@ -14,6 +14,8 @@
<enumerator ID="0x01" name="Normal World Lighting"/>
<enumerator ID="0x02" name="Unknown 2"/>
<enumerator ID="0x03" name="Disable World Lighting"/>
<enumerator ID="0x04" name="Unknown 3"/>
<enumerator ID="0x05" name="Unknown 4"/>
</enumerators>
</enum>
<property ID="0x08" name="Light Recalculation Options" type="long"/>