Rewrote SCAN asset handling + loading

This commit is contained in:
Aruki
2019-01-12 21:28:04 -08:00
parent a174548750
commit a1d94cc58f
37 changed files with 342 additions and 1296 deletions

View File

@@ -2,9 +2,10 @@
#define CANIMATIONPARAMETERS_H
#include "Core/Resource/TResPtr.h"
#include "Core/Resource/Model/CModel.h"
#include <Common/EGame.h>
class CModel;
class CAnimationParameters
{
EGame mGame;

View File

@@ -381,12 +381,12 @@ void CResTypeInfo::CResTypeInfoFactory::InitTypes()
{
CResTypeInfo *pType = new CResTypeInfo(EResourceType::StringTable, "String Table", "strg");
AddExtension(pType, "STRG", EGame::PrimeDemo, EGame::DKCReturns);
pType->mCanBeSerialized = true;
}
{
CResTypeInfo *pType = new CResTypeInfo(EResourceType::Texture, "Texture", "txtr");
AddExtension(pType, "TXTR", EGame::PrimeDemo, EGame::DKCReturns);
pType->mCanHaveDependencies = false;
pType->mCanBeSerialized = true;
}
{
CResTypeInfo *pType = new CResTypeInfo(EResourceType::Tweaks, "Tweak Data", "ctwk");

View File

@@ -1,122 +0,0 @@
#ifndef CSCAN_H
#define CSCAN_H
#include "CResource.h"
#include "TResPtr.h"
#include "Core/Resource/Animation/CAnimationParameters.h"
#include "Core/Resource/StringTable/CStringTable.h"
#include <Common/EGame.h>
class CScan : public CResource
{
DECLARE_RESOURCE_TYPE(Scan)
friend class CScanLoader;
public:
// This likely needs revising when MP2/MP3 support is added
enum class ELogbookCategory
{
None,
PirateData,
ChozoLore,
Creatures,
Research
};
struct SScanInfoSecondaryModel
{
CAssetID ModelID;
CAnimationParameters AnimParams;
TString AttachBoneName;
};
private:
// Common
TResPtr<CStringTable> mpStringTable;
bool mIsSlow;
bool mIsImportant;
ELogbookCategory mCategory;
// MP1
CAssetID mFrameID;
CAssetID mScanImageTextures[4];
// MP2/3
bool mUseLogbookModelPostScan;
CAssetID mPostOverrideTexture;
float mLogbookDefaultRotX;
float mLogbookDefaultRotZ;
float mLogbookScale;
CAssetID mLogbookModel;
CAnimationParameters mLogbookAnimParams;
CAnimationParameters mUnknownAnimParams;
std::vector<SScanInfoSecondaryModel> mSecondaryModels;
// MP3
std::vector<CAssetID> mDependencyList;
public:
CScan(CResourceEntry *pEntry = 0)
: CResource(pEntry)
, mpStringTable(nullptr)
, mIsSlow(false)
, mIsImportant(false)
, mCategory(ELogbookCategory::None)
{}
CDependencyTree* BuildDependencyTree() const
{
CDependencyTree *pTree = new CDependencyTree();
// Corruption's SCAN has a list of all assets - just grab that
if (Game() >= EGame::CorruptionProto)
{
for (uint32 iDep = 0; iDep < mDependencyList.size(); iDep++)
{
pTree->AddDependency(mDependencyList[iDep]);
}
return pTree;
}
// Otherwise add all the dependencies we need from the properties
if (Game() <= EGame::Prime)
pTree->AddDependency(mFrameID);
pTree->AddDependency(mpStringTable);
if (Game() <= EGame::Prime)
{
for (uint32 iImg = 0; iImg < 4; iImg++)
pTree->AddDependency(mScanImageTextures[iImg]);
}
else if (Game() <= EGame::Echoes)
{
pTree->AddDependency(mPostOverrideTexture);
pTree->AddDependency(mLogbookModel);
pTree->AddCharacterDependency(mLogbookAnimParams);
pTree->AddCharacterDependency(mUnknownAnimParams);
for (uint32 iSec = 0; iSec < mSecondaryModels.size(); iSec++)
{
const SScanInfoSecondaryModel& rkSecModel = mSecondaryModels[iSec];
pTree->AddDependency(rkSecModel.ModelID);
pTree->AddCharacterDependency(rkSecModel.AnimParams);
}
}
return pTree;
}
// Accessors
inline CStringTable* ScanText() const { return mpStringTable; }
inline bool IsImportant() const { return mIsImportant; }
inline bool IsSlow() const { return mIsSlow; }
inline ELogbookCategory LogbookCategory() const { return mCategory; }
inline CAssetID GuiFrame() const { return mFrameID; }
inline CAssetID ScanImage(uint32 ImgIndex) const { return mScanImageTextures[ImgIndex]; }
inline CAssetID LogbookDisplayAssetID() const { return (mLogbookAnimParams.ID().IsValid() ? mLogbookAnimParams.ID() : mLogbookModel); }
};
#endif // CSCAN_H

View File

@@ -1,306 +1,83 @@
#include "CScanLoader.h"
#include "Core/GameProject/CResourceStore.h"
#include "CScriptLoader.h"
#include <Common/Log.h>
CScanLoader::CScanLoader()
CScan* CScanLoader::LoadScanMP1(IInputStream& SCAN, CResourceEntry* pEntry)
{
}
CScan* CScanLoader::LoadScanMP1(IInputStream& rSCAN)
{
// Basic support at the moment - don't read animation/scan image data
mpScan->mFrameID = CAssetID(rSCAN, k32Bit);
mpScan->mpStringTable = gpResourceStore->LoadResource(rSCAN.ReadLong(), EResourceType::StringTable);
mpScan->mIsSlow = (rSCAN.ReadLong() != 0);
mpScan->mCategory = (CScan::ELogbookCategory) rSCAN.ReadLong();
mpScan->mIsImportant = (rSCAN.ReadByte() == 1);
for (uint32 iImg = 0; iImg < 4; iImg++)
{
mpScan->mScanImageTextures[iImg] = CAssetID(rSCAN, k32Bit);
rSCAN.Seek(0x18, SEEK_CUR);
}
return mpScan;
}
CScan* CScanLoader::LoadScanMP2(IInputStream& rSCAN)
{
// The SCAN format in MP2 embeds a SNFO object using the same format as SCLY
// However since the contents of the file are consistent there's no need to delegate to CScriptLoader
rSCAN.Skip(0x1);
uint32 NumInstances = rSCAN.ReadLong();
if (NumInstances != 1)
{
errorf("%s: SCAN has multiple instances", *rSCAN.GetSourceString());
return nullptr;
}
uint32 ScanInfoStart = rSCAN.Tell();
CFourCC SNFO(rSCAN);
if (SNFO != FOURCC('SNFO'))
{
errorf("%s [0x%X]: Unrecognized SCAN object type: %s", *rSCAN.GetSourceString(), ScanInfoStart, *SNFO.ToString());
return nullptr;
}
uint16 InstanceSize = rSCAN.ReadShort();
uint32 InstanceEnd = rSCAN.Tell() + InstanceSize;
rSCAN.Skip(0x4);
uint16 NumConnections = rSCAN.ReadShort();
if (NumConnections > 0)
{
warnf("%s [0x%X]: SNFO object in SCAN has connections", *rSCAN.GetSourceString(), ScanInfoStart);
rSCAN.Skip(NumConnections * 0xC);
}
uint32 BasePropID = rSCAN.ReadLong();
if (BasePropID != 0xFFFFFFFF)
{
errorf("%s [0x%X]: Invalid base property ID: 0x%08X", *rSCAN.GetSourceString(), rSCAN.Tell() - 4, BasePropID);
return nullptr;
}
rSCAN.Skip(0x2);
uint16 NumProperties = rSCAN.ReadShort();
switch (NumProperties)
{
case 0x14:
case 0xB:
mpScan = new CScan(mpEntry);
LoadParamsMP2(rSCAN, NumProperties);
break;
case 0x12:
case 0x15:
case 0x16:
mpScan = new CScan(mpEntry);
LoadParamsMP3(rSCAN, NumProperties);
break;
default:
errorf("%s [0x%X]: Invalid SNFO property count: 0x%X", *rSCAN.GetSourceString(), rSCAN.Tell() - 2, NumProperties);
return nullptr;
}
// Load MP3 dependency list
if (mpScan->Game() == EGame::Corruption)
{
rSCAN.GoTo(InstanceEnd);
uint32 NumDeps = rSCAN.ReadLong();
for (uint32 DepIdx = 0; DepIdx < NumDeps; DepIdx++)
{
rSCAN.Skip(4);
CAssetID ID(rSCAN, mpScan->Game());
mpScan->mDependencyList.push_back(ID);
}
}
return mpScan;
}
void CScanLoader::LoadParamsMP2(IInputStream& rSCAN, uint16 NumProperties)
{
// Function begins after the SNFO property count
mpScan->mSecondaryModels.resize(9);
for (uint32 iProp = 0; iProp < NumProperties; iProp++)
{
uint32 PropertyID = rSCAN.ReadLong();
uint16 PropertySize = rSCAN.ReadShort();
uint32 Next = rSCAN.Tell() + PropertySize;
switch (PropertyID)
{
case 0x2F5B6423:
mpScan->mpStringTable = gpResourceStore->LoadResource(rSCAN.ReadLong(), EResourceType::StringTable);
break;
case 0xC308A322:
mpScan->mIsSlow = (rSCAN.ReadLong() != 0);
break;
case 0x7B714814:
mpScan->mIsImportant = rSCAN.ReadBool();
break;
case 0x1733B1EC:
mpScan->mUseLogbookModelPostScan = rSCAN.ReadBool();
break;
case 0x53336141:
mpScan->mPostOverrideTexture = CAssetID(rSCAN, mVersion);
break;
case 0x3DE0BA64:
mpScan->mLogbookDefaultRotX = rSCAN.ReadFloat();
break;
case 0x2ADD6628:
mpScan->mLogbookDefaultRotZ = rSCAN.ReadFloat();
break;
case 0xD0C15066:
mpScan->mLogbookScale = rSCAN.ReadFloat();
break;
case 0xB7ADC418:
mpScan->mLogbookModel = CAssetID(rSCAN, mVersion);
break;
case 0x15694EE1:
mpScan->mLogbookAnimParams = CAnimationParameters(rSCAN, mVersion);
break;
case 0x58F9FE99:
mpScan->mUnknownAnimParams = CAnimationParameters(rSCAN, mVersion);
break;
case 0x1C5B4A3A:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[0] );
break;
case 0x8728A0EE:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[1] );
break;
case 0xF1CD99D3:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[2] );
break;
case 0x6ABE7307:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[3] );
break;
case 0x1C07EBA9:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[4] );
break;
case 0x8774017D:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[5] );
break;
case 0xF1913840:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[6] );
break;
case 0x6AE2D294:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[7] );
break;
case 0x1CE2091C:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[8] );
break;
}
rSCAN.GoTo(Next);
}
mpScan->mCategory = CScan::ELogbookCategory::None;
}
void CScanLoader::LoadParamsMP3(IInputStream& rSCAN, uint16 NumProperties)
{
// Function begins after the SNFO property count
for (uint32 iProp = 0; iProp < NumProperties; iProp++)
{
uint32 PropertyID = rSCAN.ReadLong();
uint16 PropertySize = rSCAN.ReadShort();
uint32 Next = rSCAN.Tell() + PropertySize;
switch (PropertyID)
{
case 0x2F5B6423:
mpScan->mpStringTable = gpResourceStore->LoadResource(rSCAN.ReadLongLong(), EResourceType::StringTable);
break;
case 0xC308A322:
mpScan->mIsSlow = (rSCAN.ReadLong() != 0);
break;
case 0x7B714814:
mpScan->mIsImportant = (rSCAN.ReadByte() != 0);
break;
}
rSCAN.GoTo(Next);
}
mpScan->mCategory = CScan::ELogbookCategory::None;
}
void CScanLoader::LoadScanInfoSecondaryModel(IInputStream& rSCAN, CScan::SScanInfoSecondaryModel& rSecondaryModel)
{
uint16 NumProperties = rSCAN.ReadShort();
for (uint32 iProp = 0; iProp < NumProperties; iProp++)
{
uint32 PropertyID = rSCAN.ReadLong();
uint16 PropertySize = rSCAN.ReadShort();
uint32 Next = rSCAN.Tell() + PropertySize;
switch (PropertyID)
{
case 0x1F7921BC:
rSecondaryModel.ModelID = CAssetID(rSCAN, mVersion);
break;
case 0xCDD202D1:
rSecondaryModel.AnimParams = CAnimationParameters(rSCAN, mVersion);
break;
case 0x3EA2BED8:
rSecondaryModel.AttachBoneName = rSCAN.ReadString();
break;
}
rSCAN.GoTo(Next);
}
}
// ************ STATIC/PUBLIC ************
CScan* CScanLoader::LoadSCAN(IInputStream& rSCAN, CResourceEntry *pEntry)
{
if (!rSCAN.IsValid()) return nullptr;
/* Switching to EGame enum here isn't really useful unfortunately
* because the MP1 demo can be 1, 2, or 3, while MP1 is 5 and MP2+ is 2
* MP1 is the only one that starts with 5 so that is a consistent check for now
* Better version checks will be implemented when the other versions are
* better-understood. */
uint32 FileVersion = rSCAN.ReadLong();
uint32 Magic = rSCAN.ReadLong();
// Echoes+
if (FileVersion == FOURCC('SCAN'))
{
// The MP2 load function will check for MP3
CScanLoader Loader;
Loader.mVersion = EGame::Echoes;
Loader.mpEntry = pEntry;
if (Magic == 0x01000000) rSCAN.Seek(-4, SEEK_CUR); // The version number isn't present in the Echoes demo
return Loader.LoadScanMP2(rSCAN);
}
// Validate magic
uint Magic = SCAN.ReadLong();
if (Magic != 0x0BADBEEF)
{
errorf("%s: Invalid SCAN magic: 0x%08X", *rSCAN.GetSourceString(), Magic);
errorf("Invalid magic in SCAN asset: 0x%08X", Magic);
return nullptr;
}
if (FileVersion != 5)
{
errorf("%s: Unsupported SCAN version: 0x%X", *rSCAN.GetSourceString(), FileVersion);
return nullptr;
}
// MP1 SCAN - read the file!
CScanLoader Loader;
Loader.mVersion = EGame::Prime;
Loader.mpScan = new CScan(pEntry);
Loader.mpEntry = pEntry;
return Loader.LoadScanMP1(rSCAN);
// The SCAN format in MP2 and later games uses the script loader to load SCAN parameters.
// The MP1 format is not loaded the same way, as far as I'm aware, and is loaded the same
// way as a normal file format... however, since we support all games, we need to support
// the script object method for proper MP2/3 support (including dealing with property names/IDs).
// So, it's simplest to use the script loader to load the MP1 SCAN format as well... that enables
// us to just create one class for all SCAN assets that works for every game.
mpScan = new CScan(pEntry);
CScriptLoader::LoadStructData(SCAN, mpScan->ScanData());
return mpScan;
}
CScan* CScanLoader::LoadScanMP2(IInputStream& SCAN, CResourceEntry* pEntry)
{
// Validate version
uint Version = SCAN.ReadLong();
if (Version != 2)
{
errorf("Unrecognized SCAN version: %d", Version);
return nullptr;
}
// The SCAN format in MP2 embeds a ScannableObjectInfo script object using the same file format as SCLY.
// As such we use CScriptLoader to load parameters, but since we don't actually want to create a script
// object, we will skip past the script object/layer header and just load the properties directly.
SCAN.Skip(0x17);
mpScan = new CScan(pEntry);
CScriptLoader::LoadStructData(SCAN, mpScan->ScanData());
return mpScan;
}
// ************ STATIC/PUBLIC ************
CScan* CScanLoader::LoadSCAN(IInputStream& SCAN, CResourceEntry *pEntry)
{
if (!SCAN.IsValid()) return nullptr;
// MP1 SCAN format starts with a version number and then follows with a magic.
// The demo can be 1, 2, or 3, while the final build is 5.
// The MP2 SCAN format starts with a 'SCAN' magic.
uint VersionCheck = SCAN.ReadLong();
// Echoes+
if (VersionCheck == FOURCC('SCAN'))
{
CScanLoader Loader;
return Loader.LoadScanMP2(SCAN, pEntry);
}
// MP1
else if (VersionCheck <= 5)
{
if (VersionCheck == 5)
{
CScanLoader Loader;
return Loader.LoadScanMP1(SCAN, pEntry);
}
else
{
errorf("%s: Unsupported SCAN version: %d", VersionCheck);
return nullptr;
}
}
else
{
errorf("Failed to identify SCAN version: 0x%X", VersionCheck);
return nullptr;
}
}

View File

@@ -7,15 +7,10 @@
class CScanLoader
{
TResPtr<CScan> mpScan;
CResourceEntry *mpEntry;
EGame mVersion;
CScanLoader();
CScan* LoadScanMP1(IInputStream& rSCAN);
CScan* LoadScanMP2(IInputStream& rSCAN);
void LoadParamsMP2(IInputStream& rSCAN, uint16 NumProperties);
void LoadParamsMP3(IInputStream& rSCAN, uint16 NumProperties);
void LoadScanInfoSecondaryModel(IInputStream& rSCAN, CScan::SScanInfoSecondaryModel& rSecondaryModel);
CScanLoader() {}
CScan* LoadScanMP1(IInputStream& SCAN, CResourceEntry* pEntry);
CScan* LoadScanMP2(IInputStream& SCAN, CResourceEntry* pEntry);
public:
static CScan* LoadSCAN(IInputStream& rSCAN, CResourceEntry *pEntry);

View File

@@ -0,0 +1,51 @@
#include "CScan.h"
CScan::CScan(CResourceEntry* pEntry /*= 0*/)
: CResource(pEntry)
{
CGameTemplate* pGameTemplate = NGameList::GetGameTemplate( Game() );
mpTemplate = pGameTemplate->FindMiscTemplate("ScannableObjectInfo");
ASSERT( mpTemplate != nullptr );
CStructProperty* pProperties = mpTemplate->Properties();
mPropertyData.resize( pProperties->DataSize() );
pProperties->Construct( mPropertyData.data() );
}
CStructRef CScan::ScanData() const
{
return CStructRef((void*) mPropertyData.data(), mpTemplate->Properties());
}
/** Convenience property accessors */
CAssetRef CScan::ScanStringPropertyRef() const
{
const uint kStringIdMP1 = 0x1;
const uint kStringIdMP2 = 0x2F5B6423;
IProperty* pProperty = mpTemplate->Properties()->ChildByID(
Game() <= EGame::Prime ? kStringIdMP1 : kStringIdMP2
);
return CAssetRef( (void*) mPropertyData.data(), pProperty );
}
CBoolRef CScan::IsCriticalPropertyRef() const
{
const uint kIsCriticalIdMP1 = 0x4;
const uint kIsCriticalIdMP2 = 0x7B714814;
IProperty* pProperty = mpTemplate->Properties()->ChildByID(
Game() <= EGame::Prime ? kIsCriticalIdMP1 : kIsCriticalIdMP2
);
return CBoolRef( (void*) mPropertyData.data(), pProperty );
}
/** CResource interface */
CDependencyTree* CScan::BuildDependencyTree() const
{
CDependencyTree* pTree = new CDependencyTree();
pTree->ParseProperties(Entry(), ScanData().Property(), ScanData().DataPointer());
return pTree;
}

View File

@@ -0,0 +1,34 @@
#ifndef CSCAN_H
#define CSCAN_H
#include <Common/Common.h>
#include "Core/Resource/Animation/CAnimationParameters.h"
#include "Core/Resource/Script/CGameTemplate.h"
#include "Core/Resource/Script/NGameList.h"
/** Scannable object parameters from SCAN assets */
class CScan : public CResource
{
DECLARE_RESOURCE_TYPE(Scan)
friend class CScanLoader;
friend class CScanCooker;
/** Script template specifying scan data layout */
CScriptTemplate* mpTemplate;
/** Scan property data */
std::vector<uint8> mPropertyData;
public:
CScan(CResourceEntry* pEntry = 0);
CStructRef ScanData() const;
/** Convenience property accessors */
CAssetRef ScanStringPropertyRef() const;
CBoolRef IsCriticalPropertyRef() const;
/** CResource interface */
virtual CDependencyTree* BuildDependencyTree() const override;
};
#endif // CSCAN_H

View File

@@ -0,0 +1,14 @@
#ifndef ELOGBOOKCATEGORY_H
#define ELOGBOOKCATEGORY_H
/** Logbook category for scannable objects in MP1, used in SCAN and SAVW */
enum class ELogbookCategory
{
None = 0,
SpacePirateData = 1,
ChozoLore = 2,
Creatures = 3,
Research = 4
};
#endif // ELOGBOOKCATEGORY_H

View File

@@ -0,0 +1,60 @@
#ifndef SSCANPARAMETERSMP1_H
#define SSCANPARAMETERSMP1_H
#include "ELogbookCategory.h"
#include <Common/Common.h>
/** Struct mapping to SCAN property layout in MP1 */
enum class EScanSpeed
{
Normal = 0,
Slow = 1,
};
enum class EScanImagePane
{
Pane0 = 0,
Pane1 = 1,
Pane2 = 2,
Pane3 = 3,
Pane01 = 4,
Pane12 = 5,
Pane23 = 6,
Pane012 = 7,
Pane123 = 8,
Pane0123 = 9,
Pane4 = 10,
Pane5 = 11,
Pane6 = 12,
Pane7 = 13,
Pane45 = 14,
Pane56 = 15,
Pane67 = 16,
Pane456 = 17,
Pane567 = 18,
Pane4567 = 19,
None = -1
};
struct SScanImage
{
CAssetID Texture;
float AppearPercentage;
EScanImagePane Pane;
int32 AnimationCellWidth;
int32 AnimationCellHeight;
float AnimationSwapInterval;
float FadeDuration;
};
struct SScanParametersMP1
{
CAssetID GuiFrame;
CAssetID String;
EScanSpeed Speed;
ELogbookCategory LogbookCategory;
bool IsCritical;
SScanImage ScanImages[4];
};
#endif // SSCANPARAMETERSMP1_H

View File

@@ -2,6 +2,7 @@
#define CANIMATIONSETPROPERTY_H
#include "IProperty.h"
#include "Core/Resource/Animation/CAnimationParameters.h"
class CAnimationSetProperty : public TSerializeableTypedProperty< CAnimationParameters, EPropertyType::AnimationSet >
{

View File

@@ -1,7 +1,6 @@
#ifndef IPROPERTY_H
#define IPROPERTY_H
#include "Core/Resource/Animation/CAnimationParameters.h"
#include <Common/Common.h>
#include <Common/CFourCC.h>
#include <Common/Math/CVector3f.h>