Implemented SCAN cooker

This commit is contained in:
Aruki
2019-01-30 13:48:05 -07:00
parent e6702d09e8
commit 1e997dac46
9 changed files with 206 additions and 18 deletions

View File

@@ -256,6 +256,7 @@ HEADERS += \
Resource/Scan/CScan.h \
Resource/Scan/SScanParametersMP1.h \
Resource/Scan/ELogbookCategory.h \
Resource/Cooker/CScanCooker.h \
NCoreTests.h
# Source Files
@@ -374,6 +375,7 @@ SOURCES += \
Tweaks/CTweakCooker.cpp \
Resource/Cooker/CStringCooker.cpp \
Resource/Scan/CScan.cpp \
Resource/Cooker/CScanCooker.cpp \
NCoreTests.cpp
# Codegen

View File

@@ -559,3 +559,82 @@ void CAreaDependencyListBuilder::EvaluateDependencyNode(CResourceEntry *pCurEntr
EvaluateDependencyNode(pCurEntry, pNode->ChildByIndex(iChild), rOut, pAudioGroupsOut);
}
}
// ************ CAssetDependencyListBuilder ************
void CAssetDependencyListBuilder::BuildDependencyList(std::vector<CAssetID>& OutAssets)
{
mCharacterUsageMap.FindUsagesForAsset(mpResourceEntry);
EvaluateDependencyNode(mpResourceEntry, mpResourceEntry->Dependencies(), OutAssets);
}
void CAssetDependencyListBuilder::AddDependency(const CAssetID& kID, std::vector<CAssetID>& Out)
{
CResourceEntry *pEntry = mpResourceEntry->ResourceStore()->FindEntry(kID);
if (!pEntry) return;
EResourceType ResType = pEntry->ResourceType();
if (mUsedAssets.find(kID) != mUsedAssets.end())
return;
// Dependency is valid! Evaluate the node tree
if (ResType == EResourceType::AnimSet)
{
ASSERT(!mCurrentAnimSetID.IsValid());
mCurrentAnimSetID = pEntry->ID();
}
EvaluateDependencyNode(pEntry, pEntry->Dependencies(), Out);
if (ResType == EResourceType::AnimSet)
{
ASSERT(mCurrentAnimSetID.IsValid());
mCurrentAnimSetID = CAssetID::InvalidID(mpResourceEntry->Game());
}
Out.push_back(kID);
mUsedAssets.insert(kID);
}
void CAssetDependencyListBuilder::EvaluateDependencyNode(CResourceEntry* pCurEntry, IDependencyNode* pNode, std::vector<CAssetID>& Out)
{
if (!pNode) return;
EDependencyNodeType Type = pNode->Type();
bool ParseChildren = false;
if (Type == EDependencyNodeType::Resource || Type == EDependencyNodeType::ScriptProperty || Type == EDependencyNodeType::CharacterProperty)
{
CResourceDependency* pDep = static_cast<CResourceDependency*>(pNode);
AddDependency(pDep->ID(), Out);
}
else if (Type == EDependencyNodeType::AnimEvent)
{
CAnimEventDependency* pDep = static_cast<CAnimEventDependency*>(pNode);
uint32 CharIndex = pDep->CharIndex();
if (CharIndex == -1 || mCharacterUsageMap.IsCharacterUsed(mCurrentAnimSetID, CharIndex))
AddDependency(pDep->ID(), Out);
}
else if (Type == EDependencyNodeType::SetCharacter)
{
CSetCharacterDependency* pChar = static_cast<CSetCharacterDependency*>(pNode);
ParseChildren = mCharacterUsageMap.IsCharacterUsed(mCurrentAnimSetID, pChar->CharSetIndex());
}
else if (Type == EDependencyNodeType::SetAnimation)
{
CSetAnimationDependency* pAnim = static_cast<CSetAnimationDependency*>(pNode);
ParseChildren = mCharacterUsageMap.IsAnimationUsed(mCurrentAnimSetID, pAnim);
}
else
ParseChildren = true;
if (ParseChildren)
{
for (uint32 iChild = 0; iChild < pNode->NumChildren(); iChild++)
EvaluateDependencyNode(pCurEntry, pNode->ChildByIndex(iChild), Out);
}
}

View File

@@ -96,5 +96,25 @@ public:
void EvaluateDependencyNode(CResourceEntry *pCurEntry, IDependencyNode *pNode, std::list<CAssetID>& rOut, std::set<CAssetID> *pAudioGroupsOut);
};
// ************ CAssetDependencyListBuilder ************
//@todo merge with CAreaDependencyListBuilder; code is very similar
class CAssetDependencyListBuilder
{
CResourceEntry* mpResourceEntry;
CCharacterUsageMap mCharacterUsageMap;
std::set<CAssetID> mUsedAssets;
CAssetID mCurrentAnimSetID;
public:
CAssetDependencyListBuilder(CResourceEntry* pEntry)
: mpResourceEntry(pEntry)
, mCharacterUsageMap(pEntry->ResourceStore())
{}
void BuildDependencyList(std::vector<CAssetID>& OutAssets);
void AddDependency(const CAssetID& kID, std::vector<CAssetID>& Out);
void EvaluateDependencyNode(CResourceEntry* pCurEntry, IDependencyNode* pNode, std::vector<CAssetID>& Out);
};
#endif // DEPENDENCYLISTBUILDERS

View File

@@ -186,6 +186,13 @@ bool ValidateCooker(EResourceType ResourceType, bool DumpInvalidFileContents)
DumpFile.WriteBytes( NewData.data(), NewData.size() );
DumpFile.Close();
}
if( NumInvalid >= 100 )
{
debugf( "Test aborted; at least 100 invalid resources. Checked %d resources, %d passed, %d failed",
NumValid + NumInvalid, NumValid, NumInvalid );
return false;
}
}
// Test complete

View File

@@ -92,7 +92,7 @@ void CAnimationParameters::Write(IOutputStream& rSCLY)
{
CAssetID::skInvalidID32.Write(rSCLY);
rSCLY.WriteLong(0);
rSCLY.WriteLong(0xFFFFFFFF);
rSCLY.WriteLong(0);
}
}

View File

@@ -4,6 +4,7 @@
#include "CAreaCooker.h"
#include "CModelCooker.h"
#include "CPoiToWorldCooker.h"
#include "CScanCooker.h"
#include "CStringCooker.h"
#include "CWorldCooker.h"
@@ -25,6 +26,7 @@ public:
{
case EResourceType::Area: return CAreaCooker::CookMREA((CGameArea*) pRes, rOutput);
case EResourceType::Model: return CModelCooker::CookCMDL((CModel*) pRes, rOutput);
case EResourceType::Scan: return CScanCooker::CookSCAN((CScan*) pRes, rOutput);
case EResourceType::StaticGeometryMap: return CPoiToWorldCooker::CookEGMC((CPoiToWorld*) pRes, rOutput);
case EResourceType::StringTable: return CStringCooker::CookSTRG((CStringTable*) pRes, rOutput);
case EResourceType::Tweaks: return CTweakCooker::CookCTWK((CTweakData*) pRes, rOutput);

View File

@@ -0,0 +1,63 @@
#include "CScanCooker.h"
#include "Core/Resource/Cooker/CScriptCooker.h"
#include "Core/GameProject/DependencyListBuilders.h"
bool CScanCooker::CookSCAN(CScan* pScan, IOutputStream& SCAN)
{
// File header
if (pScan->Game() <= EGame::Prime)
{
// We currently do not support cooking for the MP1 demo build
ASSERT( pScan->Game() != EGame::PrimeDemo );
SCAN.WriteLong( 5 ); // Version number; must be 5
SCAN.WriteLong( 0x0BADBEEF ); // SCAN magic
CStructRef ScanProperties = pScan->ScanData();
CScriptCooker Cooker(pScan->Game());
Cooker.WriteProperty(SCAN, ScanProperties.Property(), ScanProperties.DataPointer(), true);
}
else
{
SCAN.WriteFourCC( FOURCC('SCAN') ); // SCAN magic
SCAN.WriteLong( 2 ); // Version number; must be 2
SCAN.WriteByte( 1 ); // Layer version number; must be 1
SCAN.WriteLong( 1 ); // Instance count
// Scans in MP2/3 are saved with the script object data format
// Write a dummy script object header here
SCAN.WriteLong( FOURCC('SNFO') ); // ScannableObjectInfo object ID
uint ScanInstanceSizeOffset = SCAN.Tell();
SCAN.WriteShort( 0 ); // Object size
SCAN.WriteLong( 0 ); // Instance ID
SCAN.WriteShort( 0 ); // Link count
CStructRef ScanProperties = pScan->ScanData();
CScriptCooker Cooker(pScan->Game());
Cooker.WriteProperty(SCAN, ScanProperties.Property(), ScanProperties.DataPointer(), false);
uint ScanInstanceEnd = SCAN.Tell();
uint ScanInstanceSize = ScanInstanceEnd - ScanInstanceSizeOffset - 2;
SCAN.GoTo(ScanInstanceSizeOffset);
SCAN.WriteShort( (uint16) ScanInstanceSize );
SCAN.GoTo(ScanInstanceEnd);
// Write dependency list
// @todo this output may not be 100% correct. Some dependencies seem to be conditionally excluded in base game.
// This may cause some assets to be unnecessarily loaded into memory ingame.
std::vector<CAssetID> Dependencies;
CAssetDependencyListBuilder Builder(pScan->Entry());
Builder.BuildDependencyList(Dependencies);
SCAN.WriteLong(Dependencies.size());
for (const CAssetID& kID : Dependencies)
{
CResourceEntry* pEntry = pScan->Entry()->ResourceStore()->FindEntry(kID);
ASSERT( pEntry != nullptr );
pEntry->CookedExtension().Write(SCAN);
kID.Write(SCAN);
}
}
return true;
}

View File

@@ -0,0 +1,15 @@
#ifndef CSCANCOOKER_H
#define CSCANCOOKER_H
#include "Core/Resource/Scan/CScan.h"
/** Cooker class for writing game-compatible SCAN assets */
class CScanCooker
{
CScanCooker() {}
public:
static bool CookSCAN(CScan* pScan, IOutputStream& SCAN);
};
#endif // CSCANCOOKER_H