525 lines
16 KiB
C++
525 lines
16 KiB
C++
#include "CScriptLoader.h"
|
|
#include "Core/GameProject/CResourceStore.h"
|
|
#include "Core/Resource/Script/CGameTemplate.h"
|
|
#include "Core/Resource/Script/NGameList.h"
|
|
#include "Core/Resource/Script/Property/CArrayProperty.h"
|
|
#include "Core/Resource/Script/Property/CAssetProperty.h"
|
|
#include "Core/Resource/Script/Property/CEnumProperty.h"
|
|
#include "Core/Resource/Script/Property/CFlagsProperty.h"
|
|
#include <Common/Log.h>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
|
|
// Whether to ensure the values of enum/flag properties are valid
|
|
#define VALIDATE_PROPERTY_VALUES 1
|
|
|
|
CScriptLoader::CScriptLoader()
|
|
: mpObj(nullptr)
|
|
, mpCurrentData(nullptr)
|
|
{
|
|
}
|
|
|
|
void CScriptLoader::ReadProperty(IProperty *pProp, uint32 Size, IInputStream& rSCLY)
|
|
{
|
|
void* pData = (mpCurrentData ? mpCurrentData : mpObj->mPropertyData.data());
|
|
|
|
switch (pProp->Type())
|
|
{
|
|
|
|
case EPropertyType::Bool:
|
|
{
|
|
CBoolProperty* pBool = TPropCast<CBoolProperty>(pProp);
|
|
pBool->ValueRef(pData) = rSCLY.ReadBool();
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::Byte:
|
|
{
|
|
CByteProperty* pByte = TPropCast<CByteProperty>(pProp);
|
|
pByte->ValueRef(pData) = rSCLY.ReadByte();
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::Short:
|
|
{
|
|
CShortProperty* pShort = TPropCast<CShortProperty>(pProp);
|
|
pShort->ValueRef(pData) = rSCLY.ReadShort();
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::Int:
|
|
{
|
|
CIntProperty* pInt = TPropCast<CIntProperty>(pProp);
|
|
pInt->ValueRef(pData) = rSCLY.ReadLong();
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::Float:
|
|
{
|
|
CFloatProperty* pFloat = TPropCast<CFloatProperty>(pProp);
|
|
pFloat->ValueRef(pData) = rSCLY.ReadFloat();
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::Choice:
|
|
{
|
|
CChoiceProperty* pChoice = TPropCast<CChoiceProperty>(pProp);
|
|
pChoice->ValueRef(pData) = rSCLY.ReadLong();
|
|
|
|
#if VALIDATE_PROPERTY_VALUES
|
|
if (!pChoice->HasValidValue(pData))
|
|
{
|
|
uint32 Value = pChoice->ValueRef(pData);
|
|
errorf("%s [0x%X]: Choice property \"%s\" (%s) has unrecognized value: 0x%08X",
|
|
*rSCLY.GetSourceString(),
|
|
rSCLY.Tell() - 4,
|
|
*pChoice->Name(),
|
|
*pChoice->IDString(true),
|
|
Value);
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::Enum:
|
|
{
|
|
CEnumProperty* pEnum = TPropCast<CEnumProperty>(pProp);
|
|
pEnum->ValueRef(pData) = rSCLY.ReadLong();
|
|
|
|
#if VALIDATE_PROPERTY_VALUES
|
|
if (!pEnum->HasValidValue(pData))
|
|
{
|
|
uint32 Value = pEnum->ValueRef(pData);
|
|
errorf("%s [0x%X]: Enum property \"%s\" (%s) has unrecognized value: 0x%08X",
|
|
*rSCLY.GetSourceString(),
|
|
rSCLY.Tell() - 4,
|
|
*pEnum->Name(),
|
|
*pEnum->IDString(true),
|
|
Value);
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::Flags:
|
|
{
|
|
CFlagsProperty* pFlags = TPropCast<CFlagsProperty>(pProp);
|
|
pFlags->ValueRef(pData) = rSCLY.ReadLong();
|
|
|
|
#if VALIDATE_PROPERTY_VALUES
|
|
uint32 InvalidBits = pFlags->HasValidValue(pData);
|
|
|
|
if (InvalidBits)
|
|
{
|
|
warnf("%s [0x%X]: Flags property \"%s\" (%s) has unrecognized flags set: 0x%08X",
|
|
*rSCLY.GetSourceString(),
|
|
rSCLY.Tell() - 4,
|
|
*pFlags->Name(),
|
|
*pFlags->IDString(true),
|
|
InvalidBits);
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::String:
|
|
{
|
|
CStringProperty* pString = TPropCast<CStringProperty>(pProp);
|
|
pString->ValueRef(pData) = rSCLY.ReadString();
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::Vector:
|
|
{
|
|
CVectorProperty* pVector = TPropCast<CVectorProperty>(pProp);
|
|
pVector->ValueRef(pData) = CVector3f(rSCLY);
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::Color:
|
|
{
|
|
CColorProperty* pColor = TPropCast<CColorProperty>(pProp);
|
|
pColor->ValueRef(pData) = CColor(rSCLY);
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::Asset:
|
|
{
|
|
CAssetProperty* pAsset = TPropCast<CAssetProperty>(pProp);
|
|
pAsset->ValueRef(pData) = CAssetID(rSCLY, mpGameTemplate->Game());
|
|
|
|
#if VALIDATE_PROPERTY_VALUES
|
|
CAssetID ID = pAsset->ValueRef(pData);
|
|
|
|
if (ID.IsValid() && gpResourceStore)
|
|
{
|
|
CResourceEntry *pEntry = gpResourceStore->FindEntry(ID);
|
|
|
|
if (pEntry)
|
|
{
|
|
const CResTypeFilter& rkFilter = pAsset->GetTypeFilter();
|
|
bool Valid = rkFilter.Accepts(pEntry->ResourceType());
|
|
|
|
if (!Valid)
|
|
{
|
|
warnf("%s [0x%X]: Asset property \"%s\" (%s) has a reference to an illegal asset type: %s",
|
|
*rSCLY.GetSourceString(),
|
|
rSCLY.Tell() - ID.Length(),
|
|
*pAsset->Name(),
|
|
*pAsset->IDString(true),
|
|
*pEntry->CookedExtension().ToString());
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::Sound:
|
|
{
|
|
CSoundProperty* pSound = TPropCast<CSoundProperty>(pProp);
|
|
pSound->ValueRef(pData) = rSCLY.ReadLong();
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::Animation:
|
|
{
|
|
CAnimationProperty* pAnim = TPropCast<CAnimationProperty>(pProp);
|
|
pAnim->ValueRef(pData) = rSCLY.ReadLong();
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::AnimationSet:
|
|
{
|
|
CAnimationSetProperty* pAnimSet = TPropCast<CAnimationSetProperty>(pProp);
|
|
pAnimSet->ValueRef(pData) = CAnimationParameters(rSCLY, mpGameTemplate->Game());
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::Sequence:
|
|
{
|
|
// TODO
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::Spline:
|
|
{
|
|
CSplineProperty* pSpline = TPropCast<CSplineProperty>(pProp);
|
|
std::vector<char>& Buffer = pSpline->ValueRef(pData);
|
|
Buffer.resize(Size);
|
|
rSCLY.ReadBytes(Buffer.data(), Buffer.size());
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::Guid:
|
|
{
|
|
ASSERT(Size == 16);
|
|
CGuidProperty* pGuid = TPropCast<CGuidProperty>(pProp);
|
|
pGuid->ValueRef(pData).resize(16);
|
|
rSCLY.ReadBytes(pGuid->ValueRef(pData).data(), 16);
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::Struct:
|
|
{
|
|
CStructProperty* pStruct = TPropCast<CStructProperty>(pProp);
|
|
|
|
if (mVersion < EGame::EchoesDemo)
|
|
LoadStructMP1(rSCLY, pStruct);
|
|
else
|
|
LoadStructMP2(rSCLY, pStruct);
|
|
break;
|
|
}
|
|
|
|
case EPropertyType::Array:
|
|
{
|
|
CArrayProperty *pArray = TPropCast<CArrayProperty>(pProp);
|
|
int Count = rSCLY.ReadLong();
|
|
|
|
pArray->Resize(pData, Count);
|
|
void* pOldData = mpCurrentData;
|
|
|
|
// Make sure the array archetype is atomic... non-atomic array archetypes is not supported
|
|
// because arrays can only have one possible archetype so having property IDs here wouldn't make sense
|
|
ASSERT(pArray->ItemArchetype()->IsAtomic());
|
|
|
|
for (int ElementIdx = 0; ElementIdx < Count; ElementIdx++)
|
|
{
|
|
/**
|
|
* so this is kind of annoying, there isn't really any good way to cleanly integrate arrays into the property system
|
|
* because calculating the pointer to an item requires knowing the array index, which the property itself can't store
|
|
* because the same property object is used for every array element; and we can't dynamically add children to the array
|
|
* based on its size either, because the same array property is shared between multiple script instances. so, instead,
|
|
* we determine the item pointer ourselves and the array archetype property will respect it.
|
|
*
|
|
* arrays are an edge case anyway - they only really appear in Prime 1 and there are only a couple array properties in
|
|
* the game. the only situation where an array property appears in other games is SequenceTimer, and that's going to be
|
|
* migrated to Sequence properties eventually, so there isn't really any good reason to spend a lot of effort refactoring
|
|
* things to make this cleaner
|
|
*/
|
|
mpCurrentData = pArray->ItemPointer(pData, ElementIdx);
|
|
ReadProperty(pArray->ItemArchetype(), 0, rSCLY);
|
|
}
|
|
|
|
mpCurrentData = pOldData;
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void CScriptLoader::LoadStructMP1(IInputStream& rSCLY, CStructProperty* pStruct)
|
|
{
|
|
uint32 StructStart = rSCLY.Tell();
|
|
|
|
// Verify property count
|
|
uint32 PropertyCount = pStruct->NumChildren();
|
|
uint32 Version = 0;
|
|
|
|
if (!pStruct->IsAtomic())
|
|
{
|
|
uint32 FilePropCount = rSCLY.ReadLong();
|
|
//@todo version checking
|
|
}
|
|
|
|
// Parse properties
|
|
for (uint32 ChildIndex = 0; ChildIndex < PropertyCount; ChildIndex++)
|
|
{
|
|
IProperty *pProperty = pStruct->ChildByIndex(ChildIndex);
|
|
|
|
//@todo version check
|
|
if (pProperty->CookPreference() != ECookPreference::Never)
|
|
ReadProperty(pProperty, 0, rSCLY);
|
|
}
|
|
}
|
|
|
|
CScriptObject* CScriptLoader::LoadObjectMP1(IInputStream& rSCLY)
|
|
{
|
|
uint32 StartOffset = rSCLY.Tell();
|
|
uint8 Type = rSCLY.ReadByte();
|
|
uint32 Size = rSCLY.ReadLong();
|
|
uint32 End = rSCLY.Tell() + Size;
|
|
|
|
CScriptTemplate *pTemplate = mpGameTemplate->TemplateByID((uint32) Type);
|
|
if (!pTemplate)
|
|
{
|
|
// No valid template for this object; can't load
|
|
errorf("%s [0x%X]: Unknown object ID encountered: 0x%02X", *rSCLY.GetSourceString(), StartOffset, Type);
|
|
rSCLY.Seek(End, SEEK_SET);
|
|
return nullptr;
|
|
}
|
|
|
|
uint32 InstanceID = rSCLY.ReadLong() & 0x03FFFFFF;
|
|
if (InstanceID == 0x03FFFFFF) InstanceID = mpArea->FindUnusedInstanceID();
|
|
mpObj = new CScriptObject(InstanceID, mpArea, mpLayer, pTemplate);
|
|
|
|
// Load connections
|
|
uint32 NumLinks = rSCLY.ReadLong();
|
|
mpObj->mOutLinks.reserve(NumLinks);
|
|
|
|
for (uint32 iLink = 0; iLink < NumLinks; iLink++)
|
|
{
|
|
uint32 State = rSCLY.ReadLong();
|
|
uint32 Message = rSCLY.ReadLong();
|
|
uint32 ReceiverID = rSCLY.ReadLong() & 0x03FFFFFF;
|
|
|
|
CLink *pLink = new CLink(mpArea, State, Message, mpObj->mInstanceID, ReceiverID);
|
|
mpObj->mOutLinks.push_back(pLink);
|
|
}
|
|
|
|
// Load object...
|
|
CStructProperty* pProperties = pTemplate->Properties();
|
|
LoadStructMP1(rSCLY, pProperties);
|
|
|
|
// Cleanup and return
|
|
rSCLY.Seek(End, SEEK_SET);
|
|
|
|
mpObj->EvaluateProperties();
|
|
return mpObj;
|
|
}
|
|
|
|
CScriptLayer* CScriptLoader::LoadLayerMP1(IInputStream& rSCLY)
|
|
{
|
|
uint32 LayerStart = rSCLY.Tell();
|
|
|
|
rSCLY.Seek(0x1, SEEK_CUR); // One unknown byte at the start of each layer
|
|
uint32 NumObjects = rSCLY.ReadLong();
|
|
|
|
mpLayer = new CScriptLayer(mpArea);
|
|
mpLayer->Reserve(NumObjects);
|
|
|
|
for (uint32 ObjectIndex = 0; ObjectIndex < NumObjects; ObjectIndex++)
|
|
{
|
|
CScriptObject *pObject = LoadObjectMP1(rSCLY);
|
|
if (pObject)
|
|
mpLayer->AddInstance(pObject);
|
|
}
|
|
|
|
// Layer sizes are always a multiple of 32 - skip end padding before returning
|
|
uint32 Remaining = 32 - ((rSCLY.Tell() - LayerStart) & 0x1F);
|
|
rSCLY.Seek(Remaining, SEEK_CUR);
|
|
return mpLayer;
|
|
}
|
|
|
|
void CScriptLoader::LoadStructMP2(IInputStream& rSCLY, CStructProperty* pStruct)
|
|
{
|
|
// Verify property count
|
|
uint32 ChildCount = pStruct->NumChildren();
|
|
|
|
if (!pStruct->IsAtomic())
|
|
ChildCount = rSCLY.ReadShort();
|
|
|
|
// Parse properties
|
|
for (uint32 ChildIdx = 0; ChildIdx < ChildCount; ChildIdx++)
|
|
{
|
|
IProperty* pProperty = nullptr;
|
|
uint32 PropertyStart = rSCLY.Tell();
|
|
uint32 PropertyID = -1;
|
|
uint16 PropertySize = 0;
|
|
uint32 NextProperty = 0;
|
|
|
|
if (pStruct->IsAtomic())
|
|
{
|
|
pProperty = pStruct->ChildByIndex(ChildIdx);
|
|
}
|
|
else
|
|
{
|
|
PropertyID = rSCLY.ReadLong();
|
|
PropertySize = rSCLY.ReadShort();
|
|
NextProperty = rSCLY.Tell() + PropertySize;
|
|
pProperty = pStruct->ChildByID(PropertyID);
|
|
}
|
|
|
|
if (!pProperty)
|
|
errorf("%s [0x%X]: Can't find template for property 0x%08X - skipping", *rSCLY.GetSourceString(), PropertyStart, PropertyID);
|
|
else
|
|
ReadProperty(pProperty, PropertySize, rSCLY);
|
|
|
|
if (NextProperty > 0)
|
|
rSCLY.Seek(NextProperty, SEEK_SET);
|
|
}
|
|
}
|
|
|
|
CScriptObject* CScriptLoader::LoadObjectMP2(IInputStream& rSCLY)
|
|
{
|
|
uint32 ObjStart = rSCLY.Tell();
|
|
uint32 ObjectID = rSCLY.ReadLong();
|
|
uint16 ObjectSize = rSCLY.ReadShort();
|
|
uint32 ObjEnd = rSCLY.Tell() + ObjectSize;
|
|
|
|
CScriptTemplate* pTemplate = mpGameTemplate->TemplateByID(ObjectID);
|
|
|
|
if (!pTemplate)
|
|
{
|
|
errorf("%s [0x%X]: Unknown object ID encountered: %s", *rSCLY.GetSourceString(), ObjStart, *CFourCC(ObjectID).ToString());
|
|
rSCLY.Seek(ObjEnd, SEEK_SET);
|
|
return nullptr;
|
|
}
|
|
|
|
uint32 InstanceID = rSCLY.ReadLong() & 0x03FFFFFF;
|
|
if (InstanceID == 0x03FFFFFF) InstanceID = mpArea->FindUnusedInstanceID();
|
|
mpObj = new CScriptObject(InstanceID, mpArea, mpLayer, pTemplate);
|
|
|
|
// Load connections
|
|
uint32 NumConnections = rSCLY.ReadShort();
|
|
mpObj->mOutLinks.reserve(NumConnections);
|
|
|
|
for (uint32 LinkIdx = 0; LinkIdx < NumConnections; LinkIdx++)
|
|
{
|
|
uint32 State = rSCLY.ReadLong();
|
|
uint32 Message = rSCLY.ReadLong();
|
|
uint32 ReceiverID = rSCLY.ReadLong() & 0x03FFFFFF;
|
|
|
|
CLink* pLink = new CLink(mpArea, State, Message, mpObj->mInstanceID, ReceiverID);
|
|
mpObj->mOutLinks.push_back(pLink);
|
|
}
|
|
|
|
// Load object
|
|
rSCLY.Seek(0x6, SEEK_CUR); // Skip base struct ID + size
|
|
LoadStructMP2(rSCLY, pTemplate->Properties());
|
|
|
|
// Cleanup and return
|
|
rSCLY.Seek(ObjEnd, SEEK_SET);
|
|
mpObj->EvaluateProperties();
|
|
return mpObj;
|
|
}
|
|
|
|
CScriptLayer* CScriptLoader::LoadLayerMP2(IInputStream& rSCLY)
|
|
{
|
|
rSCLY.Seek(0x1, SEEK_CUR); // Skipping version. todo: verify this?
|
|
uint32 NumObjects = rSCLY.ReadLong();
|
|
|
|
mpLayer = new CScriptLayer(mpArea);
|
|
mpLayer->Reserve(NumObjects);
|
|
|
|
for (uint32 ObjectIdx = 0; ObjectIdx < NumObjects; ObjectIdx++)
|
|
{
|
|
CScriptObject* pObject = LoadObjectMP2(rSCLY);
|
|
if (pObject)
|
|
mpLayer->AddInstance(pObject);
|
|
}
|
|
|
|
return mpLayer;
|
|
}
|
|
|
|
// ************ STATIC ************
|
|
CScriptLayer* CScriptLoader::LoadLayer(IInputStream& rSCLY, CGameArea *pArea, EGame Version)
|
|
{
|
|
if (!rSCLY.IsValid()) return nullptr;
|
|
|
|
CScriptLoader Loader;
|
|
Loader.mVersion = Version;
|
|
Loader.mpGameTemplate = NGameList::GetGameTemplate(Version);
|
|
Loader.mpArea = pArea;
|
|
|
|
if (!Loader.mpGameTemplate)
|
|
{
|
|
debugf("This game doesn't have a game template; couldn't load script layer");
|
|
return nullptr;
|
|
}
|
|
|
|
if (Version <= EGame::Prime)
|
|
return Loader.LoadLayerMP1(rSCLY);
|
|
else
|
|
return Loader.LoadLayerMP2(rSCLY);
|
|
}
|
|
|
|
CScriptObject* CScriptLoader::LoadInstance(IInputStream& rSCLY, CGameArea *pArea, CScriptLayer *pLayer, EGame Version, bool ForceReturnsFormat)
|
|
{
|
|
if (!rSCLY.IsValid()) return nullptr;
|
|
|
|
CScriptLoader Loader;
|
|
Loader.mVersion = (ForceReturnsFormat ? EGame::DKCReturns : Version);
|
|
Loader.mpGameTemplate = NGameList::GetGameTemplate(Version);
|
|
Loader.mpArea = pArea;
|
|
Loader.mpLayer = pLayer;
|
|
|
|
if (!Loader.mpGameTemplate)
|
|
{
|
|
debugf("This game doesn't have a game template; couldn't load script instance");
|
|
return nullptr;
|
|
}
|
|
|
|
if (Loader.mVersion <= EGame::Prime)
|
|
return Loader.LoadObjectMP1(rSCLY);
|
|
else
|
|
return Loader.LoadObjectMP2(rSCLY);
|
|
}
|
|
|
|
void CScriptLoader::LoadStructData(IInputStream& rInput, CStructRef InStruct)
|
|
{
|
|
if (!rInput.IsValid()) return;
|
|
|
|
CScriptLoader Loader;
|
|
Loader.mVersion = InStruct.Property()->Game();
|
|
Loader.mpGameTemplate = NGameList::GetGameTemplate(Loader.mVersion);
|
|
Loader.mpArea = nullptr;
|
|
Loader.mpLayer = nullptr;
|
|
Loader.mpCurrentData = InStruct.DataPointer();
|
|
|
|
if (Loader.mVersion <= EGame::Prime)
|
|
Loader.LoadStructMP1(rInput, InStruct.Property());
|
|
else
|
|
Loader.LoadStructMP2(rInput, InStruct.Property());
|
|
}
|