Merge branch 'StringEditing'
This commit is contained in:
commit
1baa48de34
|
@ -1 +1 @@
|
||||||
Subproject commit 47d83075e0fb20786ad28c2e945a204ed1837856
|
Subproject commit 3c6a40742551d7afd0737d1293d036df69f34ec6
|
|
@ -26,6 +26,7 @@ about
|
||||||
above
|
above
|
||||||
absolute
|
absolute
|
||||||
absorb
|
absorb
|
||||||
|
accel
|
||||||
acceleration
|
acceleration
|
||||||
accumulate
|
accumulate
|
||||||
achievement
|
achievement
|
||||||
|
@ -962,6 +963,7 @@ orb
|
||||||
orbit
|
orbit
|
||||||
orbitable
|
orbitable
|
||||||
order
|
order
|
||||||
|
organic
|
||||||
orient
|
orient
|
||||||
orientation
|
orientation
|
||||||
origin
|
origin
|
||||||
|
@ -1260,6 +1262,8 @@ shoulder
|
||||||
show
|
show
|
||||||
shredder
|
shredder
|
||||||
shrink
|
shrink
|
||||||
|
shrub
|
||||||
|
shrubbery
|
||||||
shut
|
shut
|
||||||
shutdown
|
shutdown
|
||||||
side
|
side
|
||||||
|
@ -1555,6 +1559,7 @@ while
|
||||||
whip
|
whip
|
||||||
white
|
white
|
||||||
widget
|
widget
|
||||||
|
width
|
||||||
window
|
window
|
||||||
wing
|
wing
|
||||||
with
|
with
|
||||||
|
|
|
@ -109,6 +109,7 @@ HEADERS += \
|
||||||
Resource/Script/CScriptObject.h \
|
Resource/Script/CScriptObject.h \
|
||||||
Resource/Script/CScriptTemplate.h \
|
Resource/Script/CScriptTemplate.h \
|
||||||
Resource/Script/EVolumeShape.h \
|
Resource/Script/EVolumeShape.h \
|
||||||
|
Resource/StringTable/CStringTable.h \
|
||||||
Resource/CCollisionMesh.h \
|
Resource/CCollisionMesh.h \
|
||||||
Resource/CCollisionMeshGroup.h \
|
Resource/CCollisionMeshGroup.h \
|
||||||
Resource/CFont.h \
|
Resource/CFont.h \
|
||||||
|
@ -117,8 +118,6 @@ HEADERS += \
|
||||||
Resource/CMaterialPass.h \
|
Resource/CMaterialPass.h \
|
||||||
Resource/CMaterialSet.h \
|
Resource/CMaterialSet.h \
|
||||||
Resource/CResource.h \
|
Resource/CResource.h \
|
||||||
Resource/CScan.h \
|
|
||||||
Resource/CStringTable.h \
|
|
||||||
Resource/CTexture.h \
|
Resource/CTexture.h \
|
||||||
Resource/CWorld.h \
|
Resource/CWorld.h \
|
||||||
Resource/EResType.h \
|
Resource/EResType.h \
|
||||||
|
@ -246,7 +245,18 @@ HEADERS += \
|
||||||
Resource/Script/Property/CGuidProperty.h \
|
Resource/Script/Property/CGuidProperty.h \
|
||||||
Resource/Script/CGameTemplate.h \
|
Resource/Script/CGameTemplate.h \
|
||||||
Resource/Script/NPropertyMap.h \
|
Resource/Script/NPropertyMap.h \
|
||||||
Resource/Script/NGameList.h
|
Resource/Script/NGameList.h \
|
||||||
|
Resource/StringTable/ELanguage.h \
|
||||||
|
Tweaks/CTweakManager.h \
|
||||||
|
Tweaks/CTweakData.h \
|
||||||
|
Tweaks/CTweakLoader.h \
|
||||||
|
Tweaks/CTweakCooker.h \
|
||||||
|
Resource/Cooker/CStringCooker.h \
|
||||||
|
Resource/Scan/CScan.h \
|
||||||
|
Resource/Scan/SScanParametersMP1.h \
|
||||||
|
Resource/Scan/ELogbookCategory.h \
|
||||||
|
Resource/Cooker/CScanCooker.h \
|
||||||
|
NCoreTests.h
|
||||||
|
|
||||||
# Source Files
|
# Source Files
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
@ -357,7 +367,15 @@ SOURCES += \
|
||||||
Resource/Script/Property/CFlagsProperty.cpp \
|
Resource/Script/Property/CFlagsProperty.cpp \
|
||||||
Resource/Script/CGameTemplate.cpp \
|
Resource/Script/CGameTemplate.cpp \
|
||||||
Resource/Script/NPropertyMap.cpp \
|
Resource/Script/NPropertyMap.cpp \
|
||||||
Resource/Script/NGameList.cpp
|
Resource/Script/NGameList.cpp \
|
||||||
|
Resource/StringTable/CStringTable.cpp \
|
||||||
|
Tweaks/CTweakManager.cpp \
|
||||||
|
Tweaks/CTweakLoader.cpp \
|
||||||
|
Tweaks/CTweakCooker.cpp \
|
||||||
|
Resource/Cooker/CStringCooker.cpp \
|
||||||
|
Resource/Scan/CScan.cpp \
|
||||||
|
Resource/Cooker/CScanCooker.cpp \
|
||||||
|
NCoreTests.cpp
|
||||||
|
|
||||||
# Codegen
|
# Codegen
|
||||||
CODEGEN_DIR = $$EXTERNALS_DIR/CodeGen
|
CODEGEN_DIR = $$EXTERNALS_DIR/CodeGen
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
#include "CResourceIterator.h"
|
#include "CResourceIterator.h"
|
||||||
#include "Core/Resource/CAudioMacro.h"
|
#include "Core/Resource/CAudioMacro.h"
|
||||||
#include "Core/Resource/CFont.h"
|
#include "Core/Resource/CFont.h"
|
||||||
#include "Core/Resource/CScan.h"
|
|
||||||
#include "Core/Resource/CWorld.h"
|
#include "Core/Resource/CWorld.h"
|
||||||
#include "Core/Resource/Animation/CAnimSet.h"
|
#include "Core/Resource/Animation/CAnimSet.h"
|
||||||
|
#include "Core/Resource/Scan/CScan.h"
|
||||||
|
#include "Core/Resource/Scan/SScanParametersMP1.h"
|
||||||
#include "Core/Resource/Script/CScriptLayer.h"
|
#include "Core/Resource/Script/CScriptLayer.h"
|
||||||
#include <Common/Math/MathUtil.h>
|
#include <Common/Math/MathUtil.h>
|
||||||
|
|
||||||
|
@ -311,15 +312,20 @@ void GenerateAssetNames(CGameProject *pProj)
|
||||||
ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), ScanName);
|
ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), ScanName);
|
||||||
|
|
||||||
CScan *pScan = (CScan*) pEntry->Load();
|
CScan *pScan = (CScan*) pEntry->Load();
|
||||||
if (pScan && pScan->ScanText())
|
if (pScan)
|
||||||
|
{
|
||||||
|
CAssetID StringID = pScan->ScanStringPropertyRef();
|
||||||
|
CResourceEntry* pStringEntry = gpResourceStore->FindEntry(StringID);
|
||||||
|
|
||||||
|
if (pStringEntry)
|
||||||
{
|
{
|
||||||
CResourceEntry *pStringEntry = pScan->ScanText()->Entry();
|
|
||||||
ApplyGeneratedName(pStringEntry, pStringEntry->DirectoryPath(), ScanName);
|
ApplyGeneratedName(pStringEntry, pStringEntry->DirectoryPath(), ScanName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
else if (pInst->ObjectTypeID() == 0x17 || pInst->ObjectTypeID() == FOURCC('MEMO'))
|
else if (pInst->ObjectTypeID() == 0x17 || pInst->ObjectTypeID() == FOURCC('MEMO'))
|
||||||
{
|
{
|
||||||
|
@ -585,7 +591,7 @@ void GenerateAssetNames(CGameProject *pProj)
|
||||||
TString String;
|
TString String;
|
||||||
|
|
||||||
for (uint32 iStr = 0; iStr < pString->NumStrings() && String.IsEmpty(); iStr++)
|
for (uint32 iStr = 0; iStr < pString->NumStrings() && String.IsEmpty(); iStr++)
|
||||||
String = CStringTable::StripFormatting( pString->String("ENGL", iStr) ).Trimmed();
|
String = CStringTable::StripFormatting( pString->GetString(ELanguage::English, iStr) ).Trimmed();
|
||||||
|
|
||||||
if (!String.IsEmpty())
|
if (!String.IsEmpty())
|
||||||
{
|
{
|
||||||
|
@ -609,16 +615,10 @@ void GenerateAssetNames(CGameProject *pProj)
|
||||||
CScan *pScan = (CScan*) It->Load();
|
CScan *pScan = (CScan*) It->Load();
|
||||||
TString ScanName;
|
TString ScanName;
|
||||||
|
|
||||||
if (pProj->Game() >= EGame::EchoesDemo)
|
|
||||||
{
|
|
||||||
CAssetID DisplayAsset = pScan->LogbookDisplayAssetID();
|
|
||||||
CResourceEntry *pEntry = pStore->FindEntry(DisplayAsset);
|
|
||||||
if (pEntry && pEntry->IsNamed()) ScanName = pEntry->Name();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ScanName.IsEmpty())
|
if (ScanName.IsEmpty())
|
||||||
{
|
{
|
||||||
CStringTable *pString = pScan->ScanText();
|
CAssetID StringID = pScan->ScanStringPropertyRef().Get();
|
||||||
|
CStringTable *pString = (CStringTable*) gpResourceStore->LoadResource(StringID, EResourceType::StringTable);
|
||||||
if (pString) ScanName = pString->Entry()->Name();
|
if (pString) ScanName = pString->Entry()->Name();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -626,13 +626,14 @@ void GenerateAssetNames(CGameProject *pProj)
|
||||||
|
|
||||||
if (!ScanName.IsEmpty() && pProj->Game() <= EGame::Prime)
|
if (!ScanName.IsEmpty() && pProj->Game() <= EGame::Prime)
|
||||||
{
|
{
|
||||||
CAssetID FrameID = pScan->GuiFrame();
|
const SScanParametersMP1& kParms = *static_cast<SScanParametersMP1*>(pScan->ScanData().DataPointer());
|
||||||
CResourceEntry *pEntry = pStore->FindEntry(FrameID);
|
|
||||||
|
CResourceEntry *pEntry = pStore->FindEntry(kParms.GuiFrame);
|
||||||
if (pEntry) ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), "ScanFrame");
|
if (pEntry) ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), "ScanFrame");
|
||||||
|
|
||||||
for (uint32 iImg = 0; iImg < 4; iImg++)
|
for (uint32 iImg = 0; iImg < 4; iImg++)
|
||||||
{
|
{
|
||||||
CAssetID ImageID = pScan->ScanImage(iImg);
|
CAssetID ImageID = kParms.ScanImages[iImg].Texture;
|
||||||
CResourceEntry *pImgEntry = pStore->FindEntry(ImageID);
|
CResourceEntry *pImgEntry = pStore->FindEntry(ImageID);
|
||||||
if (pImgEntry) ApplyGeneratedName(pImgEntry, pImgEntry->DirectoryPath(), TString::Format("%s_Image%d", *ScanName, iImg));
|
if (pImgEntry) ApplyGeneratedName(pImgEntry, pImgEntry->DirectoryPath(), TString::Format("%s_Image%d", *ScanName, iImg));
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,70 @@ void IDependencyNode::GetAllResourceReferences(std::set<CAssetID>& rOutSet) cons
|
||||||
mChildren[iChild]->GetAllResourceReferences(rOutSet);
|
mChildren[iChild]->GetAllResourceReferences(rOutSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IDependencyNode::ParseProperties(CResourceEntry* pParentEntry, CStructProperty* pProperties, void* pData)
|
||||||
|
{
|
||||||
|
// Recursive function for parsing dependencies in properties
|
||||||
|
for (uint32 PropertyIdx = 0; PropertyIdx < pProperties->NumChildren(); PropertyIdx++)
|
||||||
|
{
|
||||||
|
IProperty* pProp = pProperties->ChildByIndex(PropertyIdx);
|
||||||
|
EPropertyType Type = pProp->Type();
|
||||||
|
|
||||||
|
// Technically we aren't parsing array children, but it's not really worth refactoring this function
|
||||||
|
// to support it when there aren't any array properties that contain any asset references anyway...
|
||||||
|
if (Type == EPropertyType::Struct)
|
||||||
|
ParseProperties( pParentEntry, TPropCast<CStructProperty>(pProp), pData );
|
||||||
|
|
||||||
|
else if (Type == EPropertyType::Sound)
|
||||||
|
{
|
||||||
|
uint32 SoundID = TPropCast<CSoundProperty>(pProp)->Value(pData);
|
||||||
|
|
||||||
|
if (SoundID != -1)
|
||||||
|
{
|
||||||
|
CGameProject* pProj = pParentEntry->Project();
|
||||||
|
SSoundInfo Info = pProj->AudioManager()->GetSoundInfo(SoundID);
|
||||||
|
|
||||||
|
if (Info.pAudioGroup)
|
||||||
|
{
|
||||||
|
CPropertyDependency *pDep = new CPropertyDependency(pProp->IDString(true), Info.pAudioGroup->ID());
|
||||||
|
mChildren.push_back(pDep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (Type == EPropertyType::Asset)
|
||||||
|
{
|
||||||
|
CAssetID ID = TPropCast<CAssetProperty>(pProp)->Value(pData);
|
||||||
|
|
||||||
|
if (ID.IsValid())
|
||||||
|
{
|
||||||
|
CPropertyDependency *pDep = new CPropertyDependency(pProp->IDString(true), ID);
|
||||||
|
mChildren.push_back(pDep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (Type == EPropertyType::AnimationSet)
|
||||||
|
{
|
||||||
|
CAnimationParameters Params = TPropCast<CAnimationSetProperty>(pProp)->Value(pData);
|
||||||
|
CAssetID ID = Params.ID();
|
||||||
|
|
||||||
|
if (ID.IsValid())
|
||||||
|
{
|
||||||
|
// Character sets are removed starting in MP3, so we only need char property dependencies in Echoes and earlier
|
||||||
|
if (pProperties->Game() <= EGame::Echoes)
|
||||||
|
{
|
||||||
|
CCharPropertyDependency *pDep = new CCharPropertyDependency(pProp->IDString(true), ID, Params.CharacterIndex());
|
||||||
|
mChildren.push_back(pDep);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CPropertyDependency *pDep = new CPropertyDependency(pProp->IDString(true), ID);
|
||||||
|
mChildren.push_back(pDep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Serialization constructor
|
// Serialization constructor
|
||||||
IDependencyNode* IDependencyNode::ArchiveConstructor(EDependencyNodeType Type)
|
IDependencyNode* IDependencyNode::ArchiveConstructor(EDependencyNodeType Type)
|
||||||
{
|
{
|
||||||
|
@ -149,76 +213,10 @@ CScriptInstanceDependency* CScriptInstanceDependency::BuildTree(CScriptObject *p
|
||||||
{
|
{
|
||||||
CScriptInstanceDependency *pInst = new CScriptInstanceDependency();
|
CScriptInstanceDependency *pInst = new CScriptInstanceDependency();
|
||||||
pInst->mObjectType = pInstance->ObjectTypeID();
|
pInst->mObjectType = pInstance->ObjectTypeID();
|
||||||
ParseStructDependencies(pInst, pInstance, pInstance->Template()->Properties());
|
pInst->ParseProperties(pInstance->Area()->Entry(), pInstance->Template()->Properties(), pInstance->PropertyData());
|
||||||
return pInst;
|
return pInst;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CScriptInstanceDependency::ParseStructDependencies(CScriptInstanceDependency* pInst, CScriptObject* pInstance, CStructProperty *pStruct)
|
|
||||||
{
|
|
||||||
// Recursive function for parsing script dependencies and loading them into the script instance dependency
|
|
||||||
void* pPropertyData = pInstance->PropertyData();
|
|
||||||
|
|
||||||
for (uint32 PropertyIdx = 0; PropertyIdx < pStruct->NumChildren(); PropertyIdx++)
|
|
||||||
{
|
|
||||||
IProperty *pProp = pStruct->ChildByIndex(PropertyIdx);
|
|
||||||
EPropertyType Type = pProp->Type();
|
|
||||||
|
|
||||||
// Technically we aren't parsing array children, but it's not really worth refactoring this function
|
|
||||||
// to support it when there aren't any array properties that contain any asset references anyway...
|
|
||||||
if (Type == EPropertyType::Struct)
|
|
||||||
ParseStructDependencies(pInst, pInstance, TPropCast<CStructProperty>(pProp));
|
|
||||||
|
|
||||||
else if (Type == EPropertyType::Sound)
|
|
||||||
{
|
|
||||||
uint32 SoundID = TPropCast<CSoundProperty>(pProp)->Value(pPropertyData);
|
|
||||||
|
|
||||||
if (SoundID != -1)
|
|
||||||
{
|
|
||||||
CGameProject *pProj = pInstance->Area()->Entry()->Project();
|
|
||||||
SSoundInfo Info = pProj->AudioManager()->GetSoundInfo(SoundID);
|
|
||||||
|
|
||||||
if (Info.pAudioGroup)
|
|
||||||
{
|
|
||||||
CPropertyDependency *pDep = new CPropertyDependency(pProp->IDString(true), Info.pAudioGroup->ID());
|
|
||||||
pInst->mChildren.push_back(pDep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (Type == EPropertyType::Asset)
|
|
||||||
{
|
|
||||||
CAssetID ID = TPropCast<CAssetProperty>(pProp)->Value(pPropertyData);
|
|
||||||
|
|
||||||
if (ID.IsValid())
|
|
||||||
{
|
|
||||||
CPropertyDependency *pDep = new CPropertyDependency(pProp->IDString(true), ID);
|
|
||||||
pInst->mChildren.push_back(pDep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (Type == EPropertyType::AnimationSet)
|
|
||||||
{
|
|
||||||
CAnimationParameters Params = TPropCast<CAnimationSetProperty>(pProp)->Value(pPropertyData);
|
|
||||||
CAssetID ID = Params.ID();
|
|
||||||
|
|
||||||
if (ID.IsValid())
|
|
||||||
{
|
|
||||||
// Character sets are removed starting in MP3, so we only need char property dependencies in Echoes and earlier
|
|
||||||
if (pStruct->Game() <= EGame::Echoes)
|
|
||||||
{
|
|
||||||
CCharPropertyDependency *pDep = new CCharPropertyDependency(pProp->IDString(true), ID, Params.CharacterIndex());
|
|
||||||
pInst->mChildren.push_back(pDep);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CPropertyDependency *pDep = new CPropertyDependency(pProp->IDString(true), ID);
|
|
||||||
pInst->mChildren.push_back(pDep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ************ CSetCharacterDependency ************
|
// ************ CSetCharacterDependency ************
|
||||||
EDependencyNodeType CSetCharacterDependency::Type() const
|
EDependencyNodeType CSetCharacterDependency::Type() const
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,6 +39,7 @@ public:
|
||||||
virtual void Serialize(IArchive& rArc) = 0;
|
virtual void Serialize(IArchive& rArc) = 0;
|
||||||
virtual void GetAllResourceReferences(std::set<CAssetID>& rOutSet) const;
|
virtual void GetAllResourceReferences(std::set<CAssetID>& rOutSet) const;
|
||||||
virtual bool HasDependency(const CAssetID& rkID) const;
|
virtual bool HasDependency(const CAssetID& rkID) const;
|
||||||
|
void ParseProperties(CResourceEntry* pParentEntry, CStructProperty* pProperties, void* pData);
|
||||||
|
|
||||||
// Serialization constructor
|
// Serialization constructor
|
||||||
static IDependencyNode* ArchiveConstructor(EDependencyNodeType Type);
|
static IDependencyNode* ArchiveConstructor(EDependencyNodeType Type);
|
||||||
|
@ -144,8 +145,6 @@ public:
|
||||||
|
|
||||||
// Static
|
// Static
|
||||||
static CScriptInstanceDependency* BuildTree(CScriptObject *pInstance);
|
static CScriptInstanceDependency* BuildTree(CScriptObject *pInstance);
|
||||||
protected:
|
|
||||||
static void ParseStructDependencies(CScriptInstanceDependency *pTree, CScriptObject* pInstance, CStructProperty *pStruct);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Node representing an animset character. Indicates what index the character is within the animset.
|
// Node representing an animset character. Indicates what index the character is within the animset.
|
||||||
|
|
|
@ -198,16 +198,16 @@ bool CGameExporter::ExtractDiscData()
|
||||||
if (IsWii)
|
if (IsWii)
|
||||||
{
|
{
|
||||||
// Extract crypto files
|
// Extract crypto files
|
||||||
if (!pDataPartition->extractCryptoFiles(*AbsDiscDir.ToUTF16(), Context))
|
if (!pDataPartition->extractCryptoFiles(ToWChar(AbsDiscDir), Context))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Extract disc header files
|
// Extract disc header files
|
||||||
if (!mpDisc->extractDiscHeaderFiles(*AbsDiscDir.ToUTF16(), Context))
|
if (!mpDisc->extractDiscHeaderFiles(ToWChar(AbsDiscDir), Context))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract system files
|
// Extract system files
|
||||||
if (!pDataPartition->extractSysFiles(*AbsDiscDir.ToUTF16(), Context))
|
if (!pDataPartition->extractSysFiles(ToWChar(AbsDiscDir), Context))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -226,7 +226,7 @@ bool CGameExporter::ExtractDiscNodeRecursive(const nod::Node *pkNode, const TStr
|
||||||
if (Iter->getKind() == nod::Node::Kind::File)
|
if (Iter->getKind() == nod::Node::Kind::File)
|
||||||
{
|
{
|
||||||
TString FilePath = rkDir + Iter->getName().data();
|
TString FilePath = rkDir + Iter->getName().data();
|
||||||
bool Success = Iter->extractToDirectory(*rkDir.ToUTF16(), rkContext);
|
bool Success = Iter->extractToDirectory(ToWChar(rkDir), rkContext);
|
||||||
if (!Success) return false;
|
if (!Success) return false;
|
||||||
|
|
||||||
if (FilePath.GetFileExtension().CaseInsensitiveCompare("pak"))
|
if (FilePath.GetFileExtension().CaseInsensitiveCompare("pak"))
|
||||||
|
@ -626,7 +626,7 @@ void CGameExporter::ExportResource(SResourceInstance& rRes)
|
||||||
Name = rRes.ResourceID.ToString();
|
Name = rRes.ResourceID.ToString();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
CResourceEntry *pEntry = mpStore->RegisterResource(rRes.ResourceID, CResTypeInfo::TypeForCookedExtension(mGame, rRes.ResourceType)->Type(), Directory, Name);
|
CResourceEntry *pEntry = mpStore->CreateNewResource(rRes.ResourceID, CResTypeInfo::TypeForCookedExtension(mGame, rRes.ResourceType)->Type(), Directory, Name);
|
||||||
|
|
||||||
// Set flags
|
// Set flags
|
||||||
pEntry->SetFlag(EResEntryFlag::IsBaseGameResource);
|
pEntry->SetFlag(EResEntryFlag::IsBaseGameResource);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "CGameProject.h"
|
#include "CGameProject.h"
|
||||||
|
#include "CResourceIterator.h"
|
||||||
#include "IUIRelay.h"
|
#include "IUIRelay.h"
|
||||||
#include "Core/Resource/Script/CGameTemplate.h"
|
#include "Core/Resource/Script/CGameTemplate.h"
|
||||||
#include <Common/Serialization/XML.h>
|
#include <Common/Serialization/XML.h>
|
||||||
|
@ -10,16 +11,12 @@ CGameProject::~CGameProject()
|
||||||
{
|
{
|
||||||
ASSERT(!mpResourceStore->IsCacheDirty());
|
ASSERT(!mpResourceStore->IsCacheDirty());
|
||||||
|
|
||||||
if (gpResourceStore == mpResourceStore)
|
if (gpResourceStore == mpResourceStore.get())
|
||||||
gpResourceStore = nullptr;
|
gpResourceStore = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (uint32 iPkg = 0; iPkg < mPackages.size(); iPkg++)
|
for (uint32 iPkg = 0; iPkg < mPackages.size(); iPkg++)
|
||||||
delete mPackages[iPkg];
|
delete mPackages[iPkg];
|
||||||
|
|
||||||
delete mpAudioManager;
|
|
||||||
delete mpGameInfo;
|
|
||||||
delete mpResourceStore;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CGameProject::Save()
|
bool CGameProject::Save()
|
||||||
|
@ -83,21 +80,21 @@ bool CGameProject::BuildISO(const TString& rkIsoPath, IProgressNotifier *pProgre
|
||||||
|
|
||||||
auto ProgressCallback = [&](float ProgressPercent, const nod::SystemStringView& rkInfoString, size_t)
|
auto ProgressCallback = [&](float ProgressPercent, const nod::SystemStringView& rkInfoString, size_t)
|
||||||
{
|
{
|
||||||
pProgress->Report((int) (ProgressPercent * 10000), 10000, TWideString(rkInfoString.data()).ToUTF8());
|
pProgress->Report((int) (ProgressPercent * 10000), 10000, nod::SystemUTF8Conv(rkInfoString).c_str());
|
||||||
};
|
};
|
||||||
|
|
||||||
pProgress->SetTask(0, "Building " + rkIsoPath.GetFileName());
|
pProgress->SetTask(0, "Building " + rkIsoPath.GetFileName());
|
||||||
TWideString DiscRoot = DiscDir(false).ToUTF16();
|
TString DiscRoot = DiscDir(false);
|
||||||
|
|
||||||
if (!IsWiiBuild())
|
if (!IsWiiBuild())
|
||||||
{
|
{
|
||||||
nod::DiscBuilderGCN Builder(*rkIsoPath.ToUTF16(), ProgressCallback);
|
nod::DiscBuilderGCN Builder(ToWChar(rkIsoPath), ProgressCallback);
|
||||||
return Builder.buildFromDirectory(*DiscRoot) == nod::EBuildResult::Success;
|
return Builder.buildFromDirectory(ToWChar(DiscRoot)) == nod::EBuildResult::Success;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
nod::DiscBuilderWii Builder(*rkIsoPath.ToUTF16(), IsTrilogy(), ProgressCallback);
|
nod::DiscBuilderWii Builder(ToWChar(rkIsoPath), IsTrilogy(), ProgressCallback);
|
||||||
return Builder.buildFromDirectory(*DiscRoot) == nod::EBuildResult::Success;
|
return Builder.buildFromDirectory(ToWChar(DiscRoot)) == nod::EBuildResult::Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,15 +106,15 @@ bool CGameProject::MergeISO(const TString& rkIsoPath, nod::DiscWii *pOriginalIso
|
||||||
|
|
||||||
auto ProgressCallback = [&](float ProgressPercent, const nod::SystemStringView& rkInfoString, size_t)
|
auto ProgressCallback = [&](float ProgressPercent, const nod::SystemStringView& rkInfoString, size_t)
|
||||||
{
|
{
|
||||||
pProgress->Report((int) (ProgressPercent * 10000), 10000, TWideString(rkInfoString.data()).ToUTF8());
|
pProgress->Report((int) (ProgressPercent * 10000), 10000, nod::SystemUTF8Conv(rkInfoString).c_str());
|
||||||
};
|
};
|
||||||
|
|
||||||
pProgress->SetTask(0, "Building " + rkIsoPath.GetFileName());
|
pProgress->SetTask(0, "Building " + rkIsoPath.GetFileName());
|
||||||
|
|
||||||
TWideString DiscRoot = DiscFilesystemRoot(false).ToUTF16();
|
TString DiscRoot = DiscFilesystemRoot(false);
|
||||||
|
|
||||||
nod::DiscMergerWii Merger(*rkIsoPath.ToUTF16(), *pOriginalIso, IsTrilogy(), ProgressCallback);
|
nod::DiscMergerWii Merger(ToWChar(rkIsoPath), *pOriginalIso, IsTrilogy(), ProgressCallback);
|
||||||
return Merger.mergeFromDirectory(*DiscRoot) == nod::EBuildResult::Success;
|
return Merger.mergeFromDirectory(ToWChar(DiscRoot)) == nod::EBuildResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CGameProject::GetWorldList(std::list<CAssetID>& rOut) const
|
void CGameProject::GetWorldList(std::list<CAssetID>& rOut) const
|
||||||
|
@ -200,7 +197,7 @@ CGameProject* CGameProject::CreateProjectForExport(
|
||||||
|
|
||||||
pProj->mProjectRoot = rkProjRootDir;
|
pProj->mProjectRoot = rkProjRootDir;
|
||||||
pProj->mProjectRoot.Replace("\\", "/");
|
pProj->mProjectRoot.Replace("\\", "/");
|
||||||
pProj->mpResourceStore = new CResourceStore(pProj);
|
pProj->mpResourceStore = std::make_unique<CResourceStore>(pProj);
|
||||||
pProj->mpGameInfo->LoadGameInfo(Game);
|
pProj->mpGameInfo->LoadGameInfo(Game);
|
||||||
return pProj;
|
return pProj;
|
||||||
}
|
}
|
||||||
|
@ -234,10 +231,14 @@ CGameProject* CGameProject::LoadProject(const TString& rkProjPath, IProgressNoti
|
||||||
{
|
{
|
||||||
// Load resource database
|
// Load resource database
|
||||||
pProgress->Report("Loading resource database");
|
pProgress->Report("Loading resource database");
|
||||||
pProj->mpResourceStore = new CResourceStore(pProj);
|
pProj->mpResourceStore = std::make_unique<CResourceStore>(pProj);
|
||||||
LoadSuccess = pProj->mpResourceStore->LoadDatabaseCache();
|
LoadSuccess = pProj->mpResourceStore->LoadDatabaseCache();
|
||||||
|
|
||||||
// Validate resource database
|
// Removed database validation step. We used to do this on project load to make sure all data was correct, but this takes a long
|
||||||
|
// time and significantly extends how long it takes to open a project. In actual practice, this isn't needed most of the time, and
|
||||||
|
// in the odd case that it is needed, there is a button in the resource browser to rebuild the database. So in the interest of
|
||||||
|
// making project startup faster, we no longer validate the database.
|
||||||
|
#if 0
|
||||||
if (LoadSuccess)
|
if (LoadSuccess)
|
||||||
{
|
{
|
||||||
pProgress->Report("Validating resource database");
|
pProgress->Report("Validating resource database");
|
||||||
|
@ -257,6 +258,7 @@ CGameProject* CGameProject::LoadProject(const TString& rkProjPath, IProgressNoti
|
||||||
LoadSuccess = false;
|
LoadSuccess = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!LoadSuccess)
|
if (!LoadSuccess)
|
||||||
|
@ -267,6 +269,44 @@ CGameProject* CGameProject::LoadProject(const TString& rkProjPath, IProgressNoti
|
||||||
|
|
||||||
pProj->mProjFileLock.Lock(ProjPath);
|
pProj->mProjFileLock.Lock(ProjPath);
|
||||||
pProj->mpGameInfo->LoadGameInfo(pProj->mGame);
|
pProj->mpGameInfo->LoadGameInfo(pProj->mGame);
|
||||||
|
|
||||||
|
// Perform update
|
||||||
|
if (Reader.FileVersion() < (uint16) EProjectVersion::Current)
|
||||||
|
{
|
||||||
|
pProgress->Report("Updating project");
|
||||||
|
|
||||||
|
CResourceStore* pOldStore = gpResourceStore;
|
||||||
|
gpResourceStore = pProj->mpResourceStore.get();
|
||||||
|
|
||||||
|
for (CResourceIterator It; It; ++It)
|
||||||
|
{
|
||||||
|
if (It->TypeInfo()->CanBeSerialized() && !It->HasRawVersion())
|
||||||
|
{
|
||||||
|
It->Save(true, false);
|
||||||
|
|
||||||
|
// Touch the cooked file to update its last modified time.
|
||||||
|
// This prevents PWE from erroneously thinking the cooked file is outdated
|
||||||
|
// (due to the raw file we just made having a more recent last modified time)
|
||||||
|
FileUtil::UpdateLastModifiedTime( It->CookedAssetPath() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pProj->mpResourceStore->ConditionalSaveStore();
|
||||||
|
pProj->Save();
|
||||||
|
|
||||||
|
gpResourceStore = pOldStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create hidden files directory, if needed
|
||||||
|
TString HiddenDir = pProj->HiddenFilesDir();
|
||||||
|
|
||||||
|
if (!FileUtil::Exists(HiddenDir))
|
||||||
|
{
|
||||||
|
FileUtil::MakeDirectory(HiddenDir);
|
||||||
|
FileUtil::MarkHidden(HiddenDir, true);
|
||||||
|
}
|
||||||
|
|
||||||
pProj->mpAudioManager->LoadAssets();
|
pProj->mpAudioManager->LoadAssets();
|
||||||
|
pProj->mpTweakManager->LoadTweaks();
|
||||||
return pProj;
|
return pProj;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "Core/CAudioManager.h"
|
#include "Core/CAudioManager.h"
|
||||||
#include "Core/IProgressNotifier.h"
|
#include "Core/IProgressNotifier.h"
|
||||||
#include "Core/Resource/Script/CGameTemplate.h"
|
#include "Core/Resource/Script/CGameTemplate.h"
|
||||||
|
#include "Core/Tweaks/CTweakManager.h"
|
||||||
#include <Common/CAssetID.h>
|
#include <Common/CAssetID.h>
|
||||||
#include <Common/EGame.h>
|
#include <Common/EGame.h>
|
||||||
#include <Common/FileUtil.h>
|
#include <Common/FileUtil.h>
|
||||||
|
@ -18,6 +19,7 @@ namespace nod { class DiscWii; }
|
||||||
enum class EProjectVersion
|
enum class EProjectVersion
|
||||||
{
|
{
|
||||||
Initial,
|
Initial,
|
||||||
|
RawStrings,
|
||||||
// Add new versions before this line
|
// Add new versions before this line
|
||||||
|
|
||||||
Max,
|
Max,
|
||||||
|
@ -34,9 +36,10 @@ class CGameProject
|
||||||
|
|
||||||
TString mProjectRoot;
|
TString mProjectRoot;
|
||||||
std::vector<CPackage*> mPackages;
|
std::vector<CPackage*> mPackages;
|
||||||
CResourceStore *mpResourceStore;
|
std::unique_ptr<CResourceStore> mpResourceStore;
|
||||||
CGameInfo *mpGameInfo;
|
std::unique_ptr<CGameInfo> mpGameInfo;
|
||||||
CAudioManager *mpAudioManager;
|
std::unique_ptr<CAudioManager> mpAudioManager;
|
||||||
|
std::unique_ptr<CTweakManager> mpTweakManager;
|
||||||
|
|
||||||
// Keep file handle open for the .prj file to prevent users from opening the same project
|
// Keep file handle open for the .prj file to prevent users from opening the same project
|
||||||
// in multiple instances of PWE
|
// in multiple instances of PWE
|
||||||
|
@ -51,14 +54,14 @@ class CGameProject
|
||||||
, mBuildVersion(0.f)
|
, mBuildVersion(0.f)
|
||||||
, mpResourceStore(nullptr)
|
, mpResourceStore(nullptr)
|
||||||
{
|
{
|
||||||
mpGameInfo = new CGameInfo();
|
mpGameInfo = std::make_unique<CGameInfo>();
|
||||||
mpAudioManager = new CAudioManager(this);
|
mpAudioManager = std::make_unique<CAudioManager>(this);
|
||||||
|
mpTweakManager = std::make_unique<CTweakManager>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~CGameProject();
|
~CGameProject();
|
||||||
|
|
||||||
|
|
||||||
bool Save();
|
bool Save();
|
||||||
bool Serialize(IArchive& rArc);
|
bool Serialize(IArchive& rArc);
|
||||||
bool BuildISO(const TString& rkIsoPath, IProgressNotifier *pProgress);
|
bool BuildISO(const TString& rkIsoPath, IProgressNotifier *pProgress);
|
||||||
|
@ -81,6 +84,7 @@ public:
|
||||||
// Directory Handling
|
// Directory Handling
|
||||||
inline TString ProjectRoot() const { return mProjectRoot; }
|
inline TString ProjectRoot() const { return mProjectRoot; }
|
||||||
inline TString ProjectPath() const { return mProjectRoot + FileUtil::SanitizeName(mProjectName, false) + ".prj"; }
|
inline TString ProjectPath() const { return mProjectRoot + FileUtil::SanitizeName(mProjectName, false) + ".prj"; }
|
||||||
|
inline TString HiddenFilesDir() const { return mProjectRoot + ".project/"; }
|
||||||
inline TString DiscDir(bool Relative) const { return Relative ? "Disc/" : mProjectRoot + "Disc/"; }
|
inline TString DiscDir(bool Relative) const { return Relative ? "Disc/" : mProjectRoot + "Disc/"; }
|
||||||
inline TString PackagesDir(bool Relative) const { return Relative ? "Packages/" : mProjectRoot + "Packages/"; }
|
inline TString PackagesDir(bool Relative) const { return Relative ? "Packages/" : mProjectRoot + "Packages/"; }
|
||||||
inline TString ResourcesDir(bool Relative) const { return Relative ? "Resources/" : mProjectRoot + "Resources/"; }
|
inline TString ResourcesDir(bool Relative) const { return Relative ? "Resources/" : mProjectRoot + "Resources/"; }
|
||||||
|
@ -95,9 +99,10 @@ public:
|
||||||
inline uint32 NumPackages() const { return mPackages.size(); }
|
inline uint32 NumPackages() const { return mPackages.size(); }
|
||||||
inline CPackage* PackageByIndex(uint32 Index) const { return mPackages[Index]; }
|
inline CPackage* PackageByIndex(uint32 Index) const { return mPackages[Index]; }
|
||||||
inline void AddPackage(CPackage *pPackage) { mPackages.push_back(pPackage); }
|
inline void AddPackage(CPackage *pPackage) { mPackages.push_back(pPackage); }
|
||||||
inline CResourceStore* ResourceStore() const { return mpResourceStore; }
|
inline CResourceStore* ResourceStore() const { return mpResourceStore.get(); }
|
||||||
inline CGameInfo* GameInfo() const { return mpGameInfo; }
|
inline CGameInfo* GameInfo() const { return mpGameInfo.get(); }
|
||||||
inline CAudioManager* AudioManager() const { return mpAudioManager; }
|
inline CAudioManager* AudioManager() const { return mpAudioManager.get(); }
|
||||||
|
inline CTweakManager* TweakManager() const { return mpTweakManager.get(); }
|
||||||
inline EGame Game() const { return mGame; }
|
inline EGame Game() const { return mGame; }
|
||||||
inline ERegion Region() const { return mRegion; }
|
inline ERegion Region() const { return mRegion; }
|
||||||
inline TString GameID() const { return mGameID; }
|
inline TString GameID() const { return mGameID; }
|
||||||
|
|
|
@ -30,7 +30,7 @@ TString COpeningBanner::EnglishGameName() const
|
||||||
Banner.ReadBytes(NameBuffer.data(), MaxLen * CharSize);
|
Banner.ReadBytes(NameBuffer.data(), MaxLen * CharSize);
|
||||||
|
|
||||||
Banner.SetData(NameBuffer.data(), NameBuffer.size(), EEndian::BigEndian);
|
Banner.SetData(NameBuffer.data(), NameBuffer.size(), EEndian::BigEndian);
|
||||||
return mWii ? Banner.ReadWString().ToUTF8() : Banner.ReadString();
|
return mWii ? Banner.Read16String().ToUTF8() : Banner.ReadString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void COpeningBanner::SetEnglishGameName(const TString& rkName)
|
void COpeningBanner::SetEnglishGameName(const TString& rkName)
|
||||||
|
@ -44,7 +44,7 @@ void COpeningBanner::SetEnglishGameName(const TString& rkName)
|
||||||
if (mWii)
|
if (mWii)
|
||||||
{
|
{
|
||||||
Banner.GoTo(0xB0);
|
Banner.GoTo(0xB0);
|
||||||
Banner.WriteWString(rkName.ToUTF16(), -1, false);
|
Banner.Write16String(rkName.ToUTF16(), -1, false);
|
||||||
PadCount = (MaxLen - rkName.Size()) * 2;
|
PadCount = (MaxLen - rkName.Size()) * 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -59,6 +59,16 @@ void CPackage::UpdateDependencyCache() const
|
||||||
mCacheDirty = false;
|
mCacheDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CPackage::MarkDirty()
|
||||||
|
{
|
||||||
|
if (!mNeedsRecook)
|
||||||
|
{
|
||||||
|
mNeedsRecook = true;
|
||||||
|
Save();
|
||||||
|
UpdateDependencyCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CPackage::Cook(IProgressNotifier *pProgress)
|
void CPackage::Cook(IProgressNotifier *pProgress)
|
||||||
{
|
{
|
||||||
SCOPED_TIMER(CookPackage);
|
SCOPED_TIMER(CookPackage);
|
||||||
|
|
|
@ -60,6 +60,7 @@ public:
|
||||||
void Serialize(IArchive& rArc);
|
void Serialize(IArchive& rArc);
|
||||||
void AddResource(const TString& rkName, const CAssetID& rkID, const CFourCC& rkType);
|
void AddResource(const TString& rkName, const CAssetID& rkID, const CFourCC& rkType);
|
||||||
void UpdateDependencyCache() const;
|
void UpdateDependencyCache() const;
|
||||||
|
void MarkDirty();
|
||||||
|
|
||||||
void Cook(IProgressNotifier *pProgress);
|
void Cook(IProgressNotifier *pProgress);
|
||||||
void CompareOriginalAssetList(const std::list<CAssetID>& rkNewList);
|
void CompareOriginalAssetList(const std::list<CAssetID>& rkNewList);
|
||||||
|
@ -77,7 +78,6 @@ public:
|
||||||
inline bool NeedsRecook() const { return mNeedsRecook; }
|
inline bool NeedsRecook() const { return mNeedsRecook; }
|
||||||
|
|
||||||
inline void SetPakName(TString NewName) { mPakName = NewName; }
|
inline void SetPakName(TString NewName) { mPakName = NewName; }
|
||||||
inline void MarkDirty() { mNeedsRecook = true; Save(); UpdateDependencyCache(); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CPACKAGE
|
#endif // CPACKAGE
|
||||||
|
|
|
@ -40,6 +40,15 @@ CResourceEntry* CResourceEntry::CreateNewResource(CResourceStore *pStore, const
|
||||||
pEntry->mpDirectory->AddChild("", pEntry);
|
pEntry->mpDirectory->AddChild("", pEntry);
|
||||||
|
|
||||||
pEntry->mMetadataDirty = true;
|
pEntry->mMetadataDirty = true;
|
||||||
|
|
||||||
|
// Check if the data exists or not. If so, then we are creating an entry for an existing resource (game exporter).
|
||||||
|
// If not, we want to initiate the new resource data and save it as soon as possible.
|
||||||
|
if (!pEntry->HasCookedVersion())
|
||||||
|
{
|
||||||
|
pEntry->mpResource = CResourceFactory::SpawnResource(pEntry);
|
||||||
|
pEntry->mpResource->InitializeNewResource();
|
||||||
|
}
|
||||||
|
|
||||||
return pEntry;
|
return pEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +113,9 @@ bool CResourceEntry::LoadMetadata()
|
||||||
|
|
||||||
bool CResourceEntry::SaveMetadata(bool ForceSave /*= false*/)
|
bool CResourceEntry::SaveMetadata(bool ForceSave /*= false*/)
|
||||||
{
|
{
|
||||||
|
// Make sure we aren't saving a deleted resource
|
||||||
|
ASSERT( !HasFlag(EResEntryFlag::MarkedForDeletion) );
|
||||||
|
|
||||||
if (mMetadataDirty || ForceSave)
|
if (mMetadataDirty || ForceSave)
|
||||||
{
|
{
|
||||||
TString Path = MetadataFilePath();
|
TString Path = MetadataFilePath();
|
||||||
|
@ -261,7 +273,7 @@ bool CResourceEntry::NeedsRecook() const
|
||||||
return (FileUtil::LastModifiedTime(CookedAssetPath()) < FileUtil::LastModifiedTime(RawAssetPath()));
|
return (FileUtil::LastModifiedTime(CookedAssetPath()) < FileUtil::LastModifiedTime(RawAssetPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CResourceEntry::Save(bool SkipCacheSave /*= false*/)
|
bool CResourceEntry::Save(bool SkipCacheSave /*= false*/, bool FlagForRecook /*= true*/)
|
||||||
{
|
{
|
||||||
// SkipCacheSave argument tells us not to save the resource cache file. This is generally not advised because we don't
|
// SkipCacheSave argument tells us not to save the resource cache file. This is generally not advised because we don't
|
||||||
// want the actual resource data to desync from the cache data. However, there are occasions where we save multiple
|
// want the actual resource data to desync from the cache data. However, there are occasions where we save multiple
|
||||||
|
@ -270,7 +282,6 @@ bool CResourceEntry::Save(bool SkipCacheSave /*= false*/)
|
||||||
//
|
//
|
||||||
// For now, always save the resource when this function is called even if there's been no changes made to it in memory.
|
// For now, always save the resource when this function is called even if there's been no changes made to it in memory.
|
||||||
// In the future this might not be desired behavior 100% of the time.
|
// In the future this might not be desired behavior 100% of the time.
|
||||||
// We also might want this function to trigger a cook for certain resource types eventually.
|
|
||||||
bool ShouldCollectGarbage = false;
|
bool ShouldCollectGarbage = false;
|
||||||
|
|
||||||
// Save raw resource
|
// Save raw resource
|
||||||
|
@ -298,8 +309,11 @@ bool CResourceEntry::Save(bool SkipCacheSave /*= false*/)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FlagForRecook)
|
||||||
|
{
|
||||||
SetFlag(EResEntryFlag::NeedsRecook);
|
SetFlag(EResEntryFlag::NeedsRecook);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This resource type doesn't have a raw format; save cooked instead
|
// This resource type doesn't have a raw format; save cooked instead
|
||||||
else
|
else
|
||||||
|
@ -321,13 +335,16 @@ bool CResourceEntry::Save(bool SkipCacheSave /*= false*/)
|
||||||
if (!SkipCacheSave)
|
if (!SkipCacheSave)
|
||||||
{
|
{
|
||||||
mpStore->ConditionalSaveStore();
|
mpStore->ConditionalSaveStore();
|
||||||
|
}
|
||||||
|
|
||||||
// Flag dirty any packages that contain this resource.
|
// Flag dirty any packages that contain this resource.
|
||||||
|
if (FlagForRecook)
|
||||||
|
{
|
||||||
for (uint32 iPkg = 0; iPkg < mpStore->Project()->NumPackages(); iPkg++)
|
for (uint32 iPkg = 0; iPkg < mpStore->Project()->NumPackages(); iPkg++)
|
||||||
{
|
{
|
||||||
CPackage *pPkg = mpStore->Project()->PackageByIndex(iPkg);
|
CPackage *pPkg = mpStore->Project()->PackageByIndex(iPkg);
|
||||||
|
|
||||||
if (pPkg->ContainsAsset(ID()))
|
if (!pPkg->NeedsRecook() && pPkg->ContainsAsset(ID()))
|
||||||
pPkg->MarkDirty();
|
pPkg->MarkDirty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -477,6 +494,9 @@ bool CResourceEntry::CanMoveTo(const TString& rkDir, const TString& rkName)
|
||||||
|
|
||||||
bool CResourceEntry::MoveAndRename(const TString& rkDir, const TString& rkName, bool IsAutoGenDir /*= false*/, bool IsAutoGenName /*= false*/)
|
bool CResourceEntry::MoveAndRename(const TString& rkDir, const TString& rkName, bool IsAutoGenDir /*= false*/, bool IsAutoGenName /*= false*/)
|
||||||
{
|
{
|
||||||
|
// Make sure we are not moving a deleted resource.
|
||||||
|
ASSERT( !IsMarkedForDeletion() );
|
||||||
|
|
||||||
if (!CanMoveTo(rkDir, rkName)) return false;
|
if (!CanMoveTo(rkDir, rkName)) return false;
|
||||||
|
|
||||||
// Store old paths
|
// Store old paths
|
||||||
|
@ -487,7 +507,15 @@ bool CResourceEntry::MoveAndRename(const TString& rkDir, const TString& rkName,
|
||||||
TString OldMetaPath = MetadataFilePath();
|
TString OldMetaPath = MetadataFilePath();
|
||||||
|
|
||||||
// Set new directory and name
|
// Set new directory and name
|
||||||
CVirtualDirectory *pNewDir = mpStore->GetVirtualDirectory(rkDir, true);
|
bool DirAlreadyExisted = true;
|
||||||
|
CVirtualDirectory *pNewDir = mpStore->GetVirtualDirectory(rkDir, false);
|
||||||
|
|
||||||
|
if (!pNewDir)
|
||||||
|
{
|
||||||
|
pNewDir = mpStore->GetVirtualDirectory(rkDir, true);
|
||||||
|
DirAlreadyExisted = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (pNewDir == mpDirectory && rkName == mName) return false;
|
if (pNewDir == mpDirectory && rkName == mName) return false;
|
||||||
|
|
||||||
// Check if we can legally move to this spot
|
// Check if we can legally move to this spot
|
||||||
|
@ -566,7 +594,7 @@ bool CResourceEntry::MoveAndRename(const TString& rkDir, const TString& rkName,
|
||||||
// If we succeeded, finish the move
|
// If we succeeded, finish the move
|
||||||
if (FSMoveSuccess)
|
if (FSMoveSuccess)
|
||||||
{
|
{
|
||||||
if (mpDirectory != pOldDir)
|
if (mpDirectory != pOldDir && pOldDir != nullptr)
|
||||||
{
|
{
|
||||||
FSMoveSuccess = pOldDir->RemoveChildResource(this);
|
FSMoveSuccess = pOldDir->RemoveChildResource(this);
|
||||||
ASSERT(FSMoveSuccess == true); // this shouldn't be able to fail
|
ASSERT(FSMoveSuccess == true); // this shouldn't be able to fail
|
||||||
|
@ -591,7 +619,11 @@ bool CResourceEntry::MoveAndRename(const TString& rkDir, const TString& rkName,
|
||||||
errorf("MOVE FAILED: %s", *MoveFailReason);
|
errorf("MOVE FAILED: %s", *MoveFailReason);
|
||||||
mpDirectory = pOldDir;
|
mpDirectory = pOldDir;
|
||||||
mName = OldName;
|
mName = OldName;
|
||||||
mpStore->ConditionalDeleteDirectory(pNewDir, false);
|
|
||||||
|
if (!DirAlreadyExisted)
|
||||||
|
{
|
||||||
|
mpStore->ConditionalDeleteDirectory(pNewDir, true);
|
||||||
|
}
|
||||||
|
|
||||||
if (FileUtil::Exists(NewRawPath))
|
if (FileUtil::Exists(NewRawPath))
|
||||||
FileUtil::MoveFile(NewRawPath, OldRawPath);
|
FileUtil::MoveFile(NewRawPath, OldRawPath);
|
||||||
|
@ -613,6 +645,96 @@ bool CResourceEntry::Rename(const TString& rkName, bool IsAutoGenName /*= false*
|
||||||
return MoveAndRename(mpDirectory->FullPath(), rkName, false, IsAutoGenName);
|
return MoveAndRename(mpDirectory->FullPath(), rkName, false, IsAutoGenName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CResourceEntry::MarkDeleted(bool InDeleted)
|
||||||
|
{
|
||||||
|
// Flags resource for future deletion. "Deleted" resources remain in memory (which
|
||||||
|
// allows them to easily be un-deleted) but cannot be looked up in the resource
|
||||||
|
// store and will not save back out to the resource database. Their file data is
|
||||||
|
// stored in a temporary directory, which allows them to be moved back if the user
|
||||||
|
// un-does the deletion.
|
||||||
|
if (IsMarkedForDeletion() != InDeleted)
|
||||||
|
{
|
||||||
|
SetFlagEnabled(EResEntryFlag::MarkedForDeletion, InDeleted);
|
||||||
|
|
||||||
|
// Restore old name/directory if un-deleting
|
||||||
|
if (!InDeleted)
|
||||||
|
{
|
||||||
|
// Our directory path is stored in the Name field - see below for explanation
|
||||||
|
int NameEnd = mName.IndexOf('|');
|
||||||
|
ASSERT( NameEnd != -1 );
|
||||||
|
|
||||||
|
TString DirPath = mName.ChopFront(NameEnd + 1);
|
||||||
|
mName = mName.ChopBack( mName.Size() - NameEnd);
|
||||||
|
mpDirectory = mpStore->GetVirtualDirectory( DirPath, true );
|
||||||
|
ASSERT( mpDirectory != nullptr );
|
||||||
|
mpDirectory->AddChild("", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
TString CookedPath = CookedAssetPath();
|
||||||
|
TString RawPath = RawAssetPath();
|
||||||
|
TString MetaPath = MetadataFilePath();
|
||||||
|
|
||||||
|
TString PathBase = mpStore->DeletedResourcePath() + mID.ToString() + ".";
|
||||||
|
TString DelCookedPath = PathBase + CookedExtension().ToString();
|
||||||
|
TString DelRawPath = DelCookedPath + ".rsraw";
|
||||||
|
TString DelMetaPath = DelCookedPath + ".rsmeta";
|
||||||
|
|
||||||
|
// If we are deleting...
|
||||||
|
if (InDeleted)
|
||||||
|
{
|
||||||
|
// Temporarily store our directory path in the name string.
|
||||||
|
// This is a hack, but we can't store the directory pointer because it may have been
|
||||||
|
// deleted and remade by the user by the time the resource is un-deleted, which
|
||||||
|
// means it is not safe to access later. Separating the name and the path with
|
||||||
|
// the '|' character is safe because this character is not allowed in filenames
|
||||||
|
// (which is enforced in FileUtil::IsValidName()).
|
||||||
|
mName = mName + "|" + mpDirectory->FullPath();
|
||||||
|
|
||||||
|
// Remove from parent directory.
|
||||||
|
mpDirectory->RemoveChildResource(this);
|
||||||
|
mpDirectory = nullptr;
|
||||||
|
|
||||||
|
// Move any resource files out of the project into a temporary folder.
|
||||||
|
FileUtil::MakeDirectory(DelMetaPath.GetFileDirectory());
|
||||||
|
|
||||||
|
if (FileUtil::Exists(MetaPath))
|
||||||
|
{
|
||||||
|
FileUtil::MoveFile(MetaPath, DelMetaPath);
|
||||||
|
}
|
||||||
|
if (FileUtil::Exists(RawPath))
|
||||||
|
{
|
||||||
|
FileUtil::MoveFile(RawPath, DelRawPath);
|
||||||
|
}
|
||||||
|
if (FileUtil::Exists(CookedPath))
|
||||||
|
{
|
||||||
|
FileUtil::MoveFile(CookedPath, DelCookedPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we are un-deleting...
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Move any resource files out of the temporary folder back into the project.
|
||||||
|
FileUtil::MakeDirectory(MetaPath.GetFileDirectory());
|
||||||
|
|
||||||
|
if (FileUtil::Exists(DelMetaPath))
|
||||||
|
{
|
||||||
|
FileUtil::MoveFile(DelMetaPath, MetaPath);
|
||||||
|
}
|
||||||
|
if (FileUtil::Exists(DelRawPath))
|
||||||
|
{
|
||||||
|
FileUtil::MoveFile(DelRawPath, RawPath);
|
||||||
|
}
|
||||||
|
if (FileUtil::Exists(DelCookedPath))
|
||||||
|
{
|
||||||
|
FileUtil::MoveFile(DelCookedPath, CookedPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mpStore->SetCacheDirty();
|
||||||
|
debugf("%s FOR DELETION: [%s] %s", InDeleted ? "MARKED" : "UNMARKED", *ID().ToString(), *CookedPath.GetFileName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CGameProject* CResourceEntry::Project() const
|
CGameProject* CResourceEntry::Project() const
|
||||||
{
|
{
|
||||||
return mpStore ? mpStore->Project() : nullptr;
|
return mpStore ? mpStore->Project() : nullptr;
|
||||||
|
|
|
@ -21,6 +21,7 @@ enum class EResEntryFlag
|
||||||
HasBeenModified = 0x00000008, // Resource has been modified and resaved by the user
|
HasBeenModified = 0x00000008, // Resource has been modified and resaved by the user
|
||||||
AutoResName = 0x00000010, // Resource name is auto-generated
|
AutoResName = 0x00000010, // Resource name is auto-generated
|
||||||
AutoResDir = 0x00000020, // Resource directory name is auto-generated
|
AutoResDir = 0x00000020, // Resource directory name is auto-generated
|
||||||
|
MarkedForDeletion = 0x00000040, // Resource has been marked for deletion by the user
|
||||||
};
|
};
|
||||||
DECLARE_FLAGS(EResEntryFlag, FResEntryFlags)
|
DECLARE_FLAGS(EResEntryFlag, FResEntryFlags)
|
||||||
|
|
||||||
|
@ -67,7 +68,7 @@ public:
|
||||||
bool IsInDirectory(CVirtualDirectory *pDir) const;
|
bool IsInDirectory(CVirtualDirectory *pDir) const;
|
||||||
uint64 Size() const;
|
uint64 Size() const;
|
||||||
bool NeedsRecook() const;
|
bool NeedsRecook() const;
|
||||||
bool Save(bool SkipCacheSave = false);
|
bool Save(bool SkipCacheSave = false, bool FlagForRecook = true);
|
||||||
bool Cook();
|
bool Cook();
|
||||||
CResource* Load();
|
CResource* Load();
|
||||||
CResource* LoadCooked(IInputStream& rInput);
|
CResource* LoadCooked(IInputStream& rInput);
|
||||||
|
@ -76,6 +77,7 @@ public:
|
||||||
bool MoveAndRename(const TString& rkDir, const TString& rkName, bool IsAutoGenDir = false, bool IsAutoGenName = false);
|
bool MoveAndRename(const TString& rkDir, const TString& rkName, bool IsAutoGenDir = false, bool IsAutoGenName = false);
|
||||||
bool Move(const TString& rkDir, bool IsAutoGenDir = false);
|
bool Move(const TString& rkDir, bool IsAutoGenDir = false);
|
||||||
bool Rename(const TString& rkName, bool IsAutoGenName = false);
|
bool Rename(const TString& rkName, bool IsAutoGenName = false);
|
||||||
|
void MarkDeleted(bool InDeleted);
|
||||||
|
|
||||||
CGameProject* Project() const;
|
CGameProject* Project() const;
|
||||||
EGame Game() const;
|
EGame Game() const;
|
||||||
|
@ -87,9 +89,10 @@ public:
|
||||||
inline void SetFlagEnabled(EResEntryFlag Flag, bool Enabled) { Enabled ? SetFlag(Flag) : ClearFlag(Flag); }
|
inline void SetFlagEnabled(EResEntryFlag Flag, bool Enabled) { Enabled ? SetFlag(Flag) : ClearFlag(Flag); }
|
||||||
|
|
||||||
inline void SetDirty() { SetFlag(EResEntryFlag::NeedsRecook); }
|
inline void SetDirty() { SetFlag(EResEntryFlag::NeedsRecook); }
|
||||||
inline void SetHidden(bool Hidden) { Hidden ? SetFlag(EResEntryFlag::Hidden) : ClearFlag(EResEntryFlag::Hidden); }
|
inline void SetHidden(bool Hidden) { SetFlagEnabled(EResEntryFlag::Hidden, Hidden); }
|
||||||
inline bool HasFlag(EResEntryFlag Flag) const { return mFlags.HasFlag(Flag); }
|
inline bool HasFlag(EResEntryFlag Flag) const { return mFlags.HasFlag(Flag); }
|
||||||
inline bool IsHidden() const { return HasFlag(EResEntryFlag::Hidden); }
|
inline bool IsHidden() const { return HasFlag(EResEntryFlag::Hidden); }
|
||||||
|
inline bool IsMarkedForDeletion() const { return HasFlag(EResEntryFlag::MarkedForDeletion); }
|
||||||
|
|
||||||
inline bool IsLoaded() const { return mpResource != nullptr; }
|
inline bool IsLoaded() const { return mpResource != nullptr; }
|
||||||
inline bool IsCategorized() const { return mpDirectory && !mpDirectory->FullPath().CaseInsensitiveCompare( mpStore->DefaultResourceDirPath() ); }
|
inline bool IsCategorized() const { return mpDirectory && !mpDirectory->FullPath().CaseInsensitiveCompare( mpStore->DefaultResourceDirPath() ); }
|
||||||
|
|
|
@ -21,6 +21,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual CResourceEntry* Next()
|
virtual CResourceEntry* Next()
|
||||||
|
{
|
||||||
|
do
|
||||||
{
|
{
|
||||||
if (mIter != mpkStore->mResourceEntries.end())
|
if (mIter != mpkStore->mResourceEntries.end())
|
||||||
{
|
{
|
||||||
|
@ -28,6 +30,8 @@ public:
|
||||||
mIter++;
|
mIter++;
|
||||||
}
|
}
|
||||||
else mpCurEntry = nullptr;
|
else mpCurEntry = nullptr;
|
||||||
|
}
|
||||||
|
while (mpCurEntry && mpCurEntry->IsMarkedForDeletion());
|
||||||
|
|
||||||
return mpCurEntry;
|
return mpCurEntry;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,22 @@ bool CResourceStore::SerializeDatabaseCache(IArchive& rArc)
|
||||||
{
|
{
|
||||||
// Serialize resources
|
// Serialize resources
|
||||||
uint32 ResourceCount = mResourceEntries.size();
|
uint32 ResourceCount = mResourceEntries.size();
|
||||||
|
|
||||||
|
if (rArc.IsWriter())
|
||||||
|
{
|
||||||
|
// Make sure deleted resources aren't included in the count.
|
||||||
|
// We can't use CResourceIterator because it skips MarkedForDeletion resources.
|
||||||
|
for (auto Iter = mResourceEntries.begin(); Iter != mResourceEntries.end(); Iter++)
|
||||||
|
{
|
||||||
|
CResourceEntry* pEntry = Iter->second;
|
||||||
|
|
||||||
|
if (pEntry->IsMarkedForDeletion())
|
||||||
|
{
|
||||||
|
ResourceCount--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rArc << SerialParameter("ResourceCount", ResourceCount);
|
rArc << SerialParameter("ResourceCount", ResourceCount);
|
||||||
|
|
||||||
if (rArc.IsReader())
|
if (rArc.IsReader())
|
||||||
|
@ -84,6 +100,8 @@ bool CResourceStore::SerializeDatabaseCache(IArchive& rArc)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (CResourceIterator It(this); It; ++It)
|
for (CResourceIterator It(this); It; ++It)
|
||||||
|
{
|
||||||
|
if (!It->IsMarkedForDeletion())
|
||||||
{
|
{
|
||||||
if (rArc.ParamBegin("Resource", 0))
|
if (rArc.ParamBegin("Resource", 0))
|
||||||
{
|
{
|
||||||
|
@ -92,6 +110,7 @@ bool CResourceStore::SerializeDatabaseCache(IArchive& rArc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
rArc.ParamEnd();
|
rArc.ParamEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,18 +159,22 @@ bool CResourceStore::LoadDatabaseCache()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Database is succesfully loaded at this point
|
// Database is successfully loaded at this point
|
||||||
if (mpProj)
|
if (mpProj)
|
||||||
|
{
|
||||||
ASSERT(mpProj->Game() == Reader.Game());
|
ASSERT(mpProj->Game() == Reader.Game());
|
||||||
}
|
}
|
||||||
|
|
||||||
mGame = Reader.Game();
|
mGame = Reader.Game();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CResourceStore::SaveDatabaseCache()
|
bool CResourceStore::SaveDatabaseCache()
|
||||||
{
|
{
|
||||||
TString Path = DatabasePath();
|
TString Path = DatabasePath();
|
||||||
|
debugf("Saving database cache...");
|
||||||
|
|
||||||
CBasicBinaryWriter Writer(Path, FOURCC('CACH'), 0, mGame);
|
CBasicBinaryWriter Writer(Path, FOURCC('CACH'), 0, mGame);
|
||||||
|
|
||||||
|
@ -182,6 +205,14 @@ void CResourceStore::SetProject(CGameProject *pProj)
|
||||||
mDatabasePath = mpProj->ProjectRoot();
|
mDatabasePath = mpProj->ProjectRoot();
|
||||||
mpDatabaseRoot = new CVirtualDirectory(this);
|
mpDatabaseRoot = new CVirtualDirectory(this);
|
||||||
mGame = mpProj->Game();
|
mGame = mpProj->Game();
|
||||||
|
|
||||||
|
// Clear deleted files from previous runs
|
||||||
|
TString DeletedPath = DeletedResourcePath();
|
||||||
|
|
||||||
|
if (FileUtil::Exists(DeletedPath))
|
||||||
|
{
|
||||||
|
FileUtil::ClearDirectory(DeletedPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,6 +246,14 @@ void CResourceStore::CloseProject()
|
||||||
It = mResourceEntries.erase(It);
|
It = mResourceEntries.erase(It);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear deleted files from previous runs
|
||||||
|
TString DeletedPath = DeletedResourcePath();
|
||||||
|
|
||||||
|
if (FileUtil::Exists(DeletedPath))
|
||||||
|
{
|
||||||
|
FileUtil::ClearDirectory(DeletedPath);
|
||||||
|
}
|
||||||
|
|
||||||
delete mpDatabaseRoot;
|
delete mpDatabaseRoot;
|
||||||
mpDatabaseRoot = nullptr;
|
mpDatabaseRoot = nullptr;
|
||||||
mpProj = nullptr;
|
mpProj = nullptr;
|
||||||
|
@ -256,12 +295,27 @@ TString CResourceStore::DefaultResourceDirPath() const
|
||||||
return StaticDefaultResourceDirPath( mGame );
|
return StaticDefaultResourceDirPath( mGame );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TString CResourceStore::DeletedResourcePath() const
|
||||||
|
{
|
||||||
|
return mpProj->HiddenFilesDir() / "delete/";
|
||||||
|
}
|
||||||
|
|
||||||
CResourceEntry* CResourceStore::FindEntry(const CAssetID& rkID) const
|
CResourceEntry* CResourceStore::FindEntry(const CAssetID& rkID) const
|
||||||
{
|
{
|
||||||
if (!rkID.IsValid()) return nullptr;
|
if (rkID.IsValid())
|
||||||
|
{
|
||||||
auto Found = mResourceEntries.find(rkID);
|
auto Found = mResourceEntries.find(rkID);
|
||||||
if (Found == mResourceEntries.end()) return nullptr;
|
|
||||||
else return Found->second;
|
if (Found != mResourceEntries.end())
|
||||||
|
{
|
||||||
|
CResourceEntry* pEntry = Found->second;
|
||||||
|
|
||||||
|
if (!pEntry->IsMarkedForDeletion())
|
||||||
|
return pEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
CResourceEntry* CResourceStore::FindEntry(const TString& rkPath) const
|
CResourceEntry* CResourceStore::FindEntry(const TString& rkPath) const
|
||||||
|
@ -284,7 +338,14 @@ void CResourceStore::ClearDatabase()
|
||||||
{
|
{
|
||||||
// THIS OPERATION REQUIRES THAT ALL RESOURCES ARE UNREFERENCED
|
// THIS OPERATION REQUIRES THAT ALL RESOURCES ARE UNREFERENCED
|
||||||
DestroyUnreferencedResources();
|
DestroyUnreferencedResources();
|
||||||
ASSERT(mLoadedResources.empty());
|
|
||||||
|
if (!mLoadedResources.empty())
|
||||||
|
{
|
||||||
|
debugf("ERROR: Resources still loaded:");
|
||||||
|
for (auto Iter = mLoadedResources.begin(); Iter != mLoadedResources.end(); Iter++)
|
||||||
|
debugf("\t[%s] %s", *Iter->first.ToString(), *Iter->second->CookedAssetPath(true));
|
||||||
|
ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
// Clear out existing resource entries and directories
|
// Clear out existing resource entries and directories
|
||||||
for (auto Iter = mResourceEntries.begin(); Iter != mResourceEntries.end(); Iter++)
|
for (auto Iter = mResourceEntries.begin(); Iter != mResourceEntries.end(); Iter++)
|
||||||
|
@ -384,7 +445,7 @@ bool CResourceStore::IsResourceRegistered(const CAssetID& rkID) const
|
||||||
return FindEntry(rkID) != nullptr;
|
return FindEntry(rkID) != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
CResourceEntry* CResourceStore::RegisterResource(const CAssetID& rkID, EResourceType Type, const TString& rkDir, const TString& rkName)
|
CResourceEntry* CResourceStore::CreateNewResource(const CAssetID& rkID, EResourceType Type, const TString& rkDir, const TString& rkName)
|
||||||
{
|
{
|
||||||
CResourceEntry *pEntry = FindEntry(rkID);
|
CResourceEntry *pEntry = FindEntry(rkID);
|
||||||
|
|
||||||
|
@ -398,6 +459,14 @@ CResourceEntry* CResourceStore::RegisterResource(const CAssetID& rkID, EResource
|
||||||
{
|
{
|
||||||
pEntry = CResourceEntry::CreateNewResource(this, rkID, rkDir, rkName, Type);
|
pEntry = CResourceEntry::CreateNewResource(this, rkID, rkDir, rkName, Type);
|
||||||
mResourceEntries[rkID] = pEntry;
|
mResourceEntries[rkID] = pEntry;
|
||||||
|
mDatabaseCacheDirty = true;
|
||||||
|
|
||||||
|
if (pEntry->IsLoaded())
|
||||||
|
{
|
||||||
|
TrackLoadedResource(pEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
debugf("CREATED NEW RESOURCE: [%s] %s", *rkID.ToString(), *pEntry->CookedAssetPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
|
@ -52,9 +52,10 @@ public:
|
||||||
void CreateVirtualDirectory(const TString& rkPath);
|
void CreateVirtualDirectory(const TString& rkPath);
|
||||||
void ConditionalDeleteDirectory(CVirtualDirectory *pDir, bool Recurse);
|
void ConditionalDeleteDirectory(CVirtualDirectory *pDir, bool Recurse);
|
||||||
TString DefaultResourceDirPath() const;
|
TString DefaultResourceDirPath() const;
|
||||||
|
TString DeletedResourcePath() const;
|
||||||
|
|
||||||
bool IsResourceRegistered(const CAssetID& rkID) const;
|
bool IsResourceRegistered(const CAssetID& rkID) const;
|
||||||
CResourceEntry* RegisterResource(const CAssetID& rkID, EResourceType Type, const TString& rkDir, const TString& rkName);
|
CResourceEntry* CreateNewResource(const CAssetID& rkID, EResourceType Type, const TString& rkDir, const TString& rkName);
|
||||||
CResourceEntry* FindEntry(const CAssetID& rkID) const;
|
CResourceEntry* FindEntry(const CAssetID& rkID) const;
|
||||||
CResourceEntry* FindEntry(const TString& rkPath) const;
|
CResourceEntry* FindEntry(const TString& rkPath) const;
|
||||||
bool AreAllEntriesValid() const;
|
bool AreAllEntriesValid() const;
|
||||||
|
|
|
@ -46,6 +46,28 @@ bool CVirtualDirectory::IsDescendantOf(CVirtualDirectory *pDir) const
|
||||||
return (this == pDir) || (mpParent && pDir && (mpParent == pDir || mpParent->IsDescendantOf(pDir)));
|
return (this == pDir) || (mpParent && pDir && (mpParent == pDir || mpParent->IsDescendantOf(pDir)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CVirtualDirectory::IsSafeToDelete() const
|
||||||
|
{
|
||||||
|
// Return false if we contain any referenced assets.
|
||||||
|
for (CResourceEntry* pEntry : mResources)
|
||||||
|
{
|
||||||
|
if (pEntry->IsLoaded() && pEntry->Resource()->IsReferenced())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (CVirtualDirectory* pSubdir : mSubdirectories)
|
||||||
|
{
|
||||||
|
if (!pSubdir->IsSafeToDelete())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
TString CVirtualDirectory::FullPath() const
|
TString CVirtualDirectory::FullPath() const
|
||||||
{
|
{
|
||||||
if (IsRoot())
|
if (IsRoot())
|
||||||
|
|
|
@ -26,6 +26,7 @@ public:
|
||||||
|
|
||||||
bool IsEmpty(bool CheckFilesystem) const;
|
bool IsEmpty(bool CheckFilesystem) const;
|
||||||
bool IsDescendantOf(CVirtualDirectory *pDir) const;
|
bool IsDescendantOf(CVirtualDirectory *pDir) const;
|
||||||
|
bool IsSafeToDelete() const;
|
||||||
TString FullPath() const;
|
TString FullPath() const;
|
||||||
TString AbsolutePath() const;
|
TString AbsolutePath() const;
|
||||||
CVirtualDirectory* GetRoot();
|
CVirtualDirectory* GetRoot();
|
||||||
|
|
|
@ -113,6 +113,7 @@ void CCharacterUsageMap::DebugPrintContents()
|
||||||
// ************ PROTECTED ************
|
// ************ PROTECTED ************
|
||||||
void CCharacterUsageMap::ParseDependencyNode(IDependencyNode *pNode)
|
void CCharacterUsageMap::ParseDependencyNode(IDependencyNode *pNode)
|
||||||
{
|
{
|
||||||
|
if (!pNode) return;
|
||||||
EDependencyNodeType Type = pNode->Type();
|
EDependencyNodeType Type = pNode->Type();
|
||||||
|
|
||||||
if (Type == EDependencyNodeType::CharacterProperty)
|
if (Type == EDependencyNodeType::CharacterProperty)
|
||||||
|
@ -271,6 +272,7 @@ void CPackageDependencyListBuilder::AddDependency(CResourceEntry *pCurEntry, con
|
||||||
|
|
||||||
void CPackageDependencyListBuilder::EvaluateDependencyNode(CResourceEntry *pCurEntry, IDependencyNode *pNode, std::list<CAssetID>& rOut)
|
void CPackageDependencyListBuilder::EvaluateDependencyNode(CResourceEntry *pCurEntry, IDependencyNode *pNode, std::list<CAssetID>& rOut)
|
||||||
{
|
{
|
||||||
|
if (!pNode) return;
|
||||||
EDependencyNodeType Type = pNode->Type();
|
EDependencyNodeType Type = pNode->Type();
|
||||||
bool ParseChildren = false;
|
bool ParseChildren = false;
|
||||||
|
|
||||||
|
@ -513,6 +515,7 @@ void CAreaDependencyListBuilder::AddDependency(const CAssetID& rkID, std::list<C
|
||||||
|
|
||||||
void CAreaDependencyListBuilder::EvaluateDependencyNode(CResourceEntry *pCurEntry, IDependencyNode *pNode, std::list<CAssetID>& rOut, std::set<CAssetID> *pAudioGroupsOut)
|
void CAreaDependencyListBuilder::EvaluateDependencyNode(CResourceEntry *pCurEntry, IDependencyNode *pNode, std::list<CAssetID>& rOut, std::set<CAssetID> *pAudioGroupsOut)
|
||||||
{
|
{
|
||||||
|
if (!pNode) return;
|
||||||
EDependencyNodeType Type = pNode->Type();
|
EDependencyNodeType Type = pNode->Type();
|
||||||
bool ParseChildren = false;
|
bool ParseChildren = false;
|
||||||
|
|
||||||
|
@ -556,3 +559,82 @@ void CAreaDependencyListBuilder::EvaluateDependencyNode(CResourceEntry *pCurEntr
|
||||||
EvaluateDependencyNode(pCurEntry, pNode->ChildByIndex(iChild), rOut, pAudioGroupsOut);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -96,5 +96,25 @@ public:
|
||||||
void EvaluateDependencyNode(CResourceEntry *pCurEntry, IDependencyNode *pNode, std::list<CAssetID>& rOut, std::set<CAssetID> *pAudioGroupsOut);
|
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
|
#endif // DEPENDENCYLISTBUILDERS
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,10 @@
|
||||||
class IUIRelay
|
class IUIRelay
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual void AsyncMessageBox(const TString& rkInfoBoxTitle, const TString& rkMessage) = 0;
|
virtual void ShowMessageBox(const TString& rkInfoBoxTitle, const TString& rkMessage) = 0;
|
||||||
|
virtual void ShowMessageBoxAsync(const TString& rkInfoBoxTitle, const TString& rkMessage) = 0;
|
||||||
virtual bool AskYesNoQuestion(const TString& rkInfoBoxTitle, const TString& rkQuestion) = 0;
|
virtual bool AskYesNoQuestion(const TString& rkInfoBoxTitle, const TString& rkQuestion) = 0;
|
||||||
|
virtual bool OpenProject(const TString& kPath = "") = 0;
|
||||||
};
|
};
|
||||||
extern IUIRelay *gpUIRelay;
|
extern IUIRelay *gpUIRelay;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,207 @@
|
||||||
|
#include "NCoreTests.h"
|
||||||
|
#include "IUIRelay.h"
|
||||||
|
#include "Core/GameProject/CGameProject.h"
|
||||||
|
#include "Core/GameProject/CResourceEntry.h"
|
||||||
|
#include "Core/GameProject/CResourceIterator.h"
|
||||||
|
#include "Core/Resource/Cooker/CResourceCooker.h"
|
||||||
|
|
||||||
|
namespace NCoreTests
|
||||||
|
{
|
||||||
|
|
||||||
|
/** Checks for a parameter in the commandline stream */
|
||||||
|
const char* ParseParameter(const char* pkParmName, int argc, char* argv[])
|
||||||
|
{
|
||||||
|
const uint kParmLen = strlen(pkParmName);
|
||||||
|
|
||||||
|
for (int i=0; i<argc; i++)
|
||||||
|
{
|
||||||
|
if( strncmp(argv[i], pkParmName, kParmLen) == 0 )
|
||||||
|
{
|
||||||
|
// Found the parameter. Make sure there is enough space in the
|
||||||
|
// string for the parameter value before returning it.
|
||||||
|
if( strlen(argv[i]) >= kParmLen+2 &&
|
||||||
|
argv[i][kParmLen] == '=')
|
||||||
|
{
|
||||||
|
return &argv[i][kParmLen+1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couldn't find the parameter.
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Checks for the existence of a token in the commandline stream */
|
||||||
|
bool ParseToken(const char* pkToken, int argc, char* argv[])
|
||||||
|
{
|
||||||
|
for (int i=0; i<argc; i++)
|
||||||
|
{
|
||||||
|
if( strcmp(argv[i], pkToken) == 0 )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couldn't find the token.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check commandline input to see if the user is running a test */
|
||||||
|
bool RunTests(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
if( ParseToken("ValidateCooker", argc, argv) )
|
||||||
|
{
|
||||||
|
// Fetch parameters
|
||||||
|
const char* pkType = ParseParameter("-type", argc, argv);
|
||||||
|
EResourceType Type = TEnumReflection<EResourceType>::ConvertStringToValue(pkType);
|
||||||
|
bool AllowDump = ParseToken("-allowdump", argc, argv);
|
||||||
|
|
||||||
|
if( Type == EResourceType::Invalid )
|
||||||
|
{
|
||||||
|
gpUIRelay->ShowMessageBox("ValidateCooker", "Usage: ValidateCooker -type=<ResourceType> [-allowdump] [-project=<Project>]");
|
||||||
|
}
|
||||||
|
else if( gpUIRelay->OpenProject(ParseParameter("-project", argc, argv)) )
|
||||||
|
{
|
||||||
|
ValidateCooker(Type, AllowDump);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No test being run.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Validate all cooker output for the given resource type matches the original asset data */
|
||||||
|
bool ValidateCooker(EResourceType ResourceType, bool DumpInvalidFileContents)
|
||||||
|
{
|
||||||
|
debugf( "Validating output of %s cooker...",
|
||||||
|
TEnumReflection<EResourceType>::ConvertValueToString(ResourceType) );
|
||||||
|
|
||||||
|
// There must be a project loaded
|
||||||
|
CResourceStore* pStore = gpResourceStore;
|
||||||
|
CGameProject* pProject = (pStore ? pStore->Project() : nullptr);
|
||||||
|
|
||||||
|
if (!pProject)
|
||||||
|
{
|
||||||
|
errorf("Cooker unit test failed; no project loaded");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TString ResourcesDir = pProject->ResourcesDir(false);
|
||||||
|
uint NumValid = 0, NumInvalid = 0;
|
||||||
|
|
||||||
|
// Iterate through all resources
|
||||||
|
for (CResourceIterator It(pStore); It; ++It)
|
||||||
|
{
|
||||||
|
if (It->ResourceType() != ResourceType || !It->HasCookedVersion())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Get original cooked data
|
||||||
|
TString CookedPath = It->CookedAssetPath(true);
|
||||||
|
CFileInStream FileStream(ResourcesDir / CookedPath, EEndian::BigEndian);
|
||||||
|
|
||||||
|
if (!FileStream.IsValid())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::vector<uint8> OriginalData( FileStream.Size() );
|
||||||
|
FileStream.ReadBytes(OriginalData.data(), OriginalData.size());
|
||||||
|
FileStream.Close();
|
||||||
|
|
||||||
|
// Generate new cooked data
|
||||||
|
std::vector<char> NewData;
|
||||||
|
CVectorOutStream MemoryStream(&NewData, EEndian::BigEndian);
|
||||||
|
CResourceCooker::CookResource(*It, MemoryStream);
|
||||||
|
|
||||||
|
// Start our comparison by making sure the sizes match up
|
||||||
|
const uint kAlignment = (It->Game() >= EGame::Corruption ? 64 : 32);
|
||||||
|
const uint kAlignedOriginalSize = ALIGN( (uint) OriginalData.size(), kAlignment );
|
||||||
|
const uint kAlignedNewSize = ALIGN( (uint) NewData.size(), kAlignment );
|
||||||
|
const char* pkInvalidReason = "";
|
||||||
|
bool IsValid = false;
|
||||||
|
|
||||||
|
if( kAlignedOriginalSize == kAlignedNewSize &&
|
||||||
|
OriginalData.size() >= NewData.size() )
|
||||||
|
{
|
||||||
|
// Compare actual data. Note that the original asset can have alignment padding
|
||||||
|
// at the end, which is applied by the pak but usually preserved in extracted
|
||||||
|
// files. We do not include this in the comparison as missing padding does not
|
||||||
|
// indicate malformed data.
|
||||||
|
uint DataSize = Math::Min(OriginalData.size(), NewData.size());
|
||||||
|
|
||||||
|
if( memcmp(OriginalData.data(), NewData.data(), DataSize) == 0 )
|
||||||
|
{
|
||||||
|
// Verify any missing data at the end is padding.
|
||||||
|
bool MissingData = false;
|
||||||
|
|
||||||
|
if( OriginalData.size() > NewData.size() )
|
||||||
|
{
|
||||||
|
for( uint i=DataSize; i<OriginalData.size(); i++ )
|
||||||
|
{
|
||||||
|
if( OriginalData[i] != 0xFF )
|
||||||
|
{
|
||||||
|
MissingData = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !MissingData )
|
||||||
|
{
|
||||||
|
// All tests passed!
|
||||||
|
IsValid = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pkInvalidReason = "missing data";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pkInvalidReason = "data mismatch";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pkInvalidReason = "size mismatch";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print test results
|
||||||
|
if( IsValid )
|
||||||
|
{
|
||||||
|
debugf( "[SUCCESS] %s", *CookedPath );
|
||||||
|
NumValid++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
debugf( "[FAILED: %s] %s", pkInvalidReason, *CookedPath );
|
||||||
|
NumInvalid++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( DumpInvalidFileContents )
|
||||||
|
{
|
||||||
|
TString DumpPath = "dump" / CookedPath;
|
||||||
|
FileUtil::MakeDirectory( DumpPath.GetFileDirectory() );
|
||||||
|
|
||||||
|
CFileOutStream DumpFile(DumpPath, EEndian::BigEndian);
|
||||||
|
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
|
||||||
|
bool TestSuccess = (NumInvalid == 0);
|
||||||
|
debugf( "Test %s; checked %d resources, %d passed, %d failed",
|
||||||
|
TestSuccess ? "SUCCEEDED" : "FAILED",
|
||||||
|
NumValid + NumInvalid, NumValid, NumInvalid );
|
||||||
|
|
||||||
|
return TestSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // end namespace NCoreTests
|
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef NCORETESTS_H
|
||||||
|
#define NCORETESTS_H
|
||||||
|
|
||||||
|
#include "Core/Resource/EResType.h"
|
||||||
|
|
||||||
|
/** Unit tests for Core */
|
||||||
|
namespace NCoreTests
|
||||||
|
{
|
||||||
|
|
||||||
|
/** Check commandline input to see if the user is running a unit test */
|
||||||
|
bool RunTests(int argc, char *argv[]);
|
||||||
|
|
||||||
|
/** Validate all cooker output for the given resource type matches the original asset data */
|
||||||
|
bool ValidateCooker(EResourceType ResourceType, bool DumpInvalidFileContents);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // NCORETESTS_H
|
|
@ -92,7 +92,7 @@ void CAnimationParameters::Write(IOutputStream& rSCLY)
|
||||||
{
|
{
|
||||||
CAssetID::skInvalidID32.Write(rSCLY);
|
CAssetID::skInvalidID32.Write(rSCLY);
|
||||||
rSCLY.WriteLong(0);
|
rSCLY.WriteLong(0);
|
||||||
rSCLY.WriteLong(0xFFFFFFFF);
|
rSCLY.WriteLong(mGame >= EGame::EchoesDemo ? 0 : 0xFFFFFFFF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
#define CANIMATIONPARAMETERS_H
|
#define CANIMATIONPARAMETERS_H
|
||||||
|
|
||||||
#include "Core/Resource/TResPtr.h"
|
#include "Core/Resource/TResPtr.h"
|
||||||
#include "Core/Resource/Model/CModel.h"
|
|
||||||
#include <Common/EGame.h>
|
#include <Common/EGame.h>
|
||||||
|
|
||||||
|
class CModel;
|
||||||
|
|
||||||
class CAnimationParameters
|
class CAnimationParameters
|
||||||
{
|
{
|
||||||
EGame mGame;
|
EGame mGame;
|
||||||
|
|
|
@ -10,6 +10,7 @@ CResTypeInfo::CResTypeInfo(EResourceType Type, const TString& rkTypeName, const
|
||||||
, mRetroExtension(rkRetroExtension)
|
, mRetroExtension(rkRetroExtension)
|
||||||
, mCanBeSerialized(false)
|
, mCanBeSerialized(false)
|
||||||
, mCanHaveDependencies(true)
|
, mCanHaveDependencies(true)
|
||||||
|
, mCanBeCreated(false)
|
||||||
{
|
{
|
||||||
#if !PUBLIC_RELEASE
|
#if !PUBLIC_RELEASE
|
||||||
ASSERT(smTypeMap.find(Type) == smTypeMap.end());
|
ASSERT(smTypeMap.find(Type) == smTypeMap.end());
|
||||||
|
@ -195,7 +196,7 @@ void CResTypeInfo::CResTypeInfoFactory::InitTypes()
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
CResTypeInfo *pType = new CResTypeInfo(EResourceType::AudioGroup, "Audio Group", "agsc");
|
CResTypeInfo *pType = new CResTypeInfo(EResourceType::AudioGroup, "Audio Group", "agsc");
|
||||||
AddExtension(pType, "AGSC", EGame::PrimeDemo, EGame::Echoes);
|
AddExtension(pType, "AGSC", EGame::PrimeDemo, EGame::CorruptionProto);
|
||||||
pType->mCanHaveDependencies = false;
|
pType->mCanHaveDependencies = false;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
@ -332,6 +333,7 @@ void CResTypeInfo::CResTypeInfoFactory::InitTypes()
|
||||||
{
|
{
|
||||||
CResTypeInfo *pType = new CResTypeInfo(EResourceType::Scan, "Scan", "scan");
|
CResTypeInfo *pType = new CResTypeInfo(EResourceType::Scan, "Scan", "scan");
|
||||||
AddExtension(pType, "SCAN", EGame::PrimeDemo, EGame::Corruption);
|
AddExtension(pType, "SCAN", EGame::PrimeDemo, EGame::Corruption);
|
||||||
|
pType->mCanBeCreated = true;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
CResTypeInfo *pType = new CResTypeInfo(EResourceType::Skeleton, "Skeleton", "cin");
|
CResTypeInfo *pType = new CResTypeInfo(EResourceType::Skeleton, "Skeleton", "cin");
|
||||||
|
@ -381,6 +383,8 @@ void CResTypeInfo::CResTypeInfoFactory::InitTypes()
|
||||||
{
|
{
|
||||||
CResTypeInfo *pType = new CResTypeInfo(EResourceType::StringTable, "String Table", "strg");
|
CResTypeInfo *pType = new CResTypeInfo(EResourceType::StringTable, "String Table", "strg");
|
||||||
AddExtension(pType, "STRG", EGame::PrimeDemo, EGame::DKCReturns);
|
AddExtension(pType, "STRG", EGame::PrimeDemo, EGame::DKCReturns);
|
||||||
|
pType->mCanBeSerialized = true;
|
||||||
|
pType->mCanBeCreated = true;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
CResTypeInfo *pType = new CResTypeInfo(EResourceType::Texture, "Texture", "txtr");
|
CResTypeInfo *pType = new CResTypeInfo(EResourceType::Texture, "Texture", "txtr");
|
||||||
|
@ -388,7 +392,7 @@ void CResTypeInfo::CResTypeInfoFactory::InitTypes()
|
||||||
pType->mCanHaveDependencies = false;
|
pType->mCanHaveDependencies = false;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
CResTypeInfo *pType = new CResTypeInfo(EResourceType::Tweak, "Tweak Data", "ctwk");
|
CResTypeInfo *pType = new CResTypeInfo(EResourceType::Tweaks, "Tweak Data", "ctwk");
|
||||||
AddExtension(pType, "CTWK", EGame::PrimeDemo, EGame::Prime);
|
AddExtension(pType, "CTWK", EGame::PrimeDemo, EGame::Prime);
|
||||||
pType->mCanHaveDependencies = false;
|
pType->mCanHaveDependencies = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ class CResTypeInfo
|
||||||
TString mRetroExtension; // File extension in Retro's directory tree. We don't use it directly but it is needed for generating asset ID hashes
|
TString mRetroExtension; // File extension in Retro's directory tree. We don't use it directly but it is needed for generating asset ID hashes
|
||||||
bool mCanBeSerialized;
|
bool mCanBeSerialized;
|
||||||
bool mCanHaveDependencies;
|
bool mCanHaveDependencies;
|
||||||
|
bool mCanBeCreated;
|
||||||
|
|
||||||
static std::unordered_map<EResourceType, CResTypeInfo*> smTypeMap;
|
static std::unordered_map<EResourceType, CResTypeInfo*> smTypeMap;
|
||||||
|
|
||||||
|
@ -40,6 +41,7 @@ public:
|
||||||
inline TString TypeName() const { return mTypeName; }
|
inline TString TypeName() const { return mTypeName; }
|
||||||
inline bool CanBeSerialized() const { return mCanBeSerialized; }
|
inline bool CanBeSerialized() const { return mCanBeSerialized; }
|
||||||
inline bool CanHaveDependencies() const { return mCanHaveDependencies; }
|
inline bool CanHaveDependencies() const { return mCanHaveDependencies; }
|
||||||
|
inline bool CanBeCreated() const { return mCanBeCreated; }
|
||||||
|
|
||||||
// Static
|
// Static
|
||||||
static void GetAllTypesInGame(EGame Game, std::list<CResTypeInfo*>& rOut);
|
static void GetAllTypesInGame(EGame Game, std::list<CResTypeInfo*>& rOut);
|
||||||
|
|
|
@ -43,6 +43,7 @@ public:
|
||||||
virtual ~CResource() {}
|
virtual ~CResource() {}
|
||||||
virtual CDependencyTree* BuildDependencyTree() const { return new CDependencyTree(); }
|
virtual CDependencyTree* BuildDependencyTree() const { return new CDependencyTree(); }
|
||||||
virtual void Serialize(IArchive& /*rArc*/) {}
|
virtual void Serialize(IArchive& /*rArc*/) {}
|
||||||
|
virtual void InitializeNewResource() {}
|
||||||
|
|
||||||
inline CResourceEntry* Entry() const { return mpEntry; }
|
inline CResourceEntry* Entry() const { return mpEntry; }
|
||||||
inline CResTypeInfo* TypeInfo() const { return mpEntry->TypeInfo(); }
|
inline CResTypeInfo* TypeInfo() const { return mpEntry->TypeInfo(); }
|
||||||
|
|
|
@ -1,122 +0,0 @@
|
||||||
#ifndef CSCAN_H
|
|
||||||
#define CSCAN_H
|
|
||||||
|
|
||||||
#include "CResource.h"
|
|
||||||
#include "CStringTable.h"
|
|
||||||
#include "TResPtr.h"
|
|
||||||
#include "Core/Resource/Animation/CAnimationParameters.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
|
|
|
@ -1,180 +0,0 @@
|
||||||
#ifndef CSTRINGTABLE_H
|
|
||||||
#define CSTRINGTABLE_H
|
|
||||||
|
|
||||||
#include "CResource.h"
|
|
||||||
#include <Common/BasicTypes.h>
|
|
||||||
#include <Common/CFourCC.h>
|
|
||||||
#include <Common/TString.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class CStringTable : public CResource
|
|
||||||
{
|
|
||||||
DECLARE_RESOURCE_TYPE(StringTable)
|
|
||||||
friend class CStringLoader;
|
|
||||||
|
|
||||||
std::vector<TString> mStringNames;
|
|
||||||
uint32 mNumStrings;
|
|
||||||
|
|
||||||
struct SLangTable
|
|
||||||
{
|
|
||||||
CFourCC Language;
|
|
||||||
std::vector<TString> Strings;
|
|
||||||
};
|
|
||||||
std::vector<SLangTable> mLangTables;
|
|
||||||
|
|
||||||
public:
|
|
||||||
CStringTable(CResourceEntry *pEntry = 0) : CResource(pEntry) {}
|
|
||||||
|
|
||||||
inline uint32 NumStrings() const { return mNumStrings; }
|
|
||||||
inline uint32 NumLanguages() const { return mLangTables.size(); }
|
|
||||||
inline CFourCC LanguageTag(uint32 Index) const { return mLangTables[Index].Language; }
|
|
||||||
inline TString String(uint32 LangIndex, uint32 StringIndex) const { return mLangTables[LangIndex].Strings[StringIndex]; }
|
|
||||||
inline TString StringName(uint32 StringIndex) const { return mStringNames[StringIndex]; }
|
|
||||||
|
|
||||||
TString String(CFourCC Lang, uint32 StringIndex) const
|
|
||||||
{
|
|
||||||
for (uint32 iLang = 0; iLang < NumLanguages(); iLang++)
|
|
||||||
{
|
|
||||||
if (LanguageTag(iLang) == Lang)
|
|
||||||
return String(iLang, StringIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return TString();
|
|
||||||
}
|
|
||||||
|
|
||||||
CDependencyTree* BuildDependencyTree() const
|
|
||||||
{
|
|
||||||
// STRGs can reference FONTs with the &font=; formatting tag and TXTRs with the &image=; tag
|
|
||||||
CDependencyTree *pTree = new CDependencyTree();
|
|
||||||
EIDLength IDLength = (Game() <= EGame::Echoes ? k32Bit : k64Bit);
|
|
||||||
|
|
||||||
for (uint32 iLang = 0; iLang < mLangTables.size(); iLang++)
|
|
||||||
{
|
|
||||||
const SLangTable& rkTable = mLangTables[iLang];
|
|
||||||
|
|
||||||
for (uint32 iStr = 0; iStr < rkTable.Strings.size(); iStr++)
|
|
||||||
{
|
|
||||||
const TString& rkStr = rkTable.Strings[iStr];
|
|
||||||
|
|
||||||
for (uint32 TagIdx = rkStr.IndexOf('&'); TagIdx != -1; TagIdx = rkStr.IndexOf('&', TagIdx + 1))
|
|
||||||
{
|
|
||||||
// Check for double ampersand (escape character in DKCR, not sure about other games)
|
|
||||||
if (rkStr.At(TagIdx + 1) == '&')
|
|
||||||
{
|
|
||||||
TagIdx++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get tag name and parameters
|
|
||||||
uint32 NameEnd = rkStr.IndexOf('=', TagIdx);
|
|
||||||
uint32 TagEnd = rkStr.IndexOf(';', TagIdx);
|
|
||||||
if (NameEnd == -1 || TagEnd == -1) continue;
|
|
||||||
|
|
||||||
TString TagName = rkStr.SubString(TagIdx + 1, NameEnd - TagIdx - 1);
|
|
||||||
TString ParamString = rkStr.SubString(NameEnd + 1, TagEnd - NameEnd - 1);
|
|
||||||
if (ParamString.IsEmpty()) continue;
|
|
||||||
|
|
||||||
// Font
|
|
||||||
if (TagName == "font")
|
|
||||||
{
|
|
||||||
if (Game() >= EGame::CorruptionProto)
|
|
||||||
{
|
|
||||||
ASSERT(ParamString.StartsWith("0x"));
|
|
||||||
ParamString = ParamString.ChopFront(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT(ParamString.Size() == IDLength * 2);
|
|
||||||
pTree->AddDependency( CAssetID::FromString(ParamString) );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Image
|
|
||||||
else if (TagName == "image")
|
|
||||||
{
|
|
||||||
// Determine which params are textures based on image type
|
|
||||||
TStringList Params = ParamString.Split(",");
|
|
||||||
TString ImageType = Params.front();
|
|
||||||
uint32 TexturesStart = -1;
|
|
||||||
|
|
||||||
if (ImageType == "A")
|
|
||||||
TexturesStart = 2;
|
|
||||||
|
|
||||||
else if (ImageType == "SI")
|
|
||||||
TexturesStart = 3;
|
|
||||||
|
|
||||||
else if (ImageType == "SA")
|
|
||||||
TexturesStart = 4;
|
|
||||||
|
|
||||||
else if (ImageType == "B")
|
|
||||||
TexturesStart = 2;
|
|
||||||
|
|
||||||
else if (ImageType.IsHexString(false, IDLength * 2))
|
|
||||||
TexturesStart = 0;
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
errorf("Unrecognized image type: %s", *ImageType);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load texture IDs
|
|
||||||
TStringList::iterator Iter = Params.begin();
|
|
||||||
|
|
||||||
for (uint32 iParam = 0; iParam < Params.size(); iParam++, Iter++)
|
|
||||||
{
|
|
||||||
if (iParam >= TexturesStart)
|
|
||||||
{
|
|
||||||
TString Param = *Iter;
|
|
||||||
|
|
||||||
if (Game() >= EGame::CorruptionProto)
|
|
||||||
{
|
|
||||||
ASSERT(Param.StartsWith("0x"));
|
|
||||||
Param = Param.ChopFront(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT(Param.Size() == IDLength * 2);
|
|
||||||
pTree->AddDependency( CAssetID::FromString(Param) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pTree;
|
|
||||||
}
|
|
||||||
|
|
||||||
static TString StripFormatting(const TString& rkStr)
|
|
||||||
{
|
|
||||||
TString Out = rkStr;
|
|
||||||
int TagStart = -1;
|
|
||||||
|
|
||||||
for (uint32 iChr = 0; iChr < Out.Size(); iChr++)
|
|
||||||
{
|
|
||||||
if (Out[iChr] == '&')
|
|
||||||
{
|
|
||||||
if (TagStart == -1)
|
|
||||||
TagStart = iChr;
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Out.Remove(TagStart, 1);
|
|
||||||
TagStart = -1;
|
|
||||||
iChr--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (TagStart != -1 && Out[iChr] == ';')
|
|
||||||
{
|
|
||||||
int TagEnd = iChr + 1;
|
|
||||||
int TagLen = TagEnd - TagStart;
|
|
||||||
Out.Remove(TagStart, TagLen);
|
|
||||||
iChr = TagStart - 1;
|
|
||||||
TagStart = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Out;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // CSTRINGTABLE_H
|
|
|
@ -63,7 +63,7 @@ void CWorld::SetAreaLayerInfo(CGameArea *pArea)
|
||||||
TString CWorld::InGameName() const
|
TString CWorld::InGameName() const
|
||||||
{
|
{
|
||||||
if (mpWorldName)
|
if (mpWorldName)
|
||||||
return mpWorldName->String("ENGL", 0);
|
return mpWorldName->GetString(ELanguage::English, 0);
|
||||||
else
|
else
|
||||||
return Entry()->Name();
|
return Entry()->Name();
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ TString CWorld::AreaInGameName(uint32 AreaIndex) const
|
||||||
const SArea& rkArea = mAreas[AreaIndex];
|
const SArea& rkArea = mAreas[AreaIndex];
|
||||||
|
|
||||||
if (rkArea.pAreaName)
|
if (rkArea.pAreaName)
|
||||||
return rkArea.pAreaName->String("ENGL", 0);
|
return rkArea.pAreaName->GetString(ELanguage::English, 0);
|
||||||
else
|
else
|
||||||
return "!!" + rkArea.InternalName;
|
return "!!" + rkArea.InternalName;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
|
|
||||||
#include "CResource.h"
|
#include "CResource.h"
|
||||||
#include "CSavedStateID.h"
|
#include "CSavedStateID.h"
|
||||||
#include "CStringTable.h"
|
|
||||||
#include "Core/Resource/Area/CGameArea.h"
|
#include "Core/Resource/Area/CGameArea.h"
|
||||||
#include "Core/Resource/Model/CModel.h"
|
#include "Core/Resource/Model/CModel.h"
|
||||||
|
#include "Core/Resource/StringTable/CStringTable.h"
|
||||||
#include <Common/Math/CTransform4f.h>
|
#include <Common/Math/CTransform4f.h>
|
||||||
|
|
||||||
class CWorld : public CResource
|
class CWorld : public CResource
|
||||||
|
|
|
@ -4,8 +4,12 @@
|
||||||
#include "CAreaCooker.h"
|
#include "CAreaCooker.h"
|
||||||
#include "CModelCooker.h"
|
#include "CModelCooker.h"
|
||||||
#include "CPoiToWorldCooker.h"
|
#include "CPoiToWorldCooker.h"
|
||||||
|
#include "CScanCooker.h"
|
||||||
|
#include "CStringCooker.h"
|
||||||
#include "CWorldCooker.h"
|
#include "CWorldCooker.h"
|
||||||
|
|
||||||
|
#include "Core/Tweaks/CTweakCooker.h"
|
||||||
|
|
||||||
#include "Core/GameProject/CResourceEntry.h"
|
#include "Core/GameProject/CResourceEntry.h"
|
||||||
|
|
||||||
class CResourceCooker
|
class CResourceCooker
|
||||||
|
@ -22,7 +26,10 @@ public:
|
||||||
{
|
{
|
||||||
case EResourceType::Area: return CAreaCooker::CookMREA((CGameArea*) pRes, rOutput);
|
case EResourceType::Area: return CAreaCooker::CookMREA((CGameArea*) pRes, rOutput);
|
||||||
case EResourceType::Model: return CModelCooker::CookCMDL((CModel*) 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::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);
|
||||||
case EResourceType::World: return CWorldCooker::CookMLVL((CWorld*) pRes, rOutput);
|
case EResourceType::World: return CWorldCooker::CookMLVL((CWorld*) pRes, rOutput);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -5,10 +5,9 @@
|
||||||
#include <Core/Resource/Script/Property/CEnumProperty.h>
|
#include <Core/Resource/Script/Property/CEnumProperty.h>
|
||||||
#include <Core/Resource/Script/Property/CFlagsProperty.h>
|
#include <Core/Resource/Script/Property/CFlagsProperty.h>
|
||||||
|
|
||||||
void CScriptCooker::WriteProperty(IOutputStream& rOut, IProperty* pProperty, bool InAtomicStruct)
|
void CScriptCooker::WriteProperty(IOutputStream& rOut, IProperty* pProperty, void* pData, bool InAtomicStruct)
|
||||||
{
|
{
|
||||||
uint32 SizeOffset = 0, PropStart = 0;
|
uint32 SizeOffset = 0, PropStart = 0;
|
||||||
void* pData = (mpArrayItemData ? mpArrayItemData : mpObject->PropertyData());
|
|
||||||
|
|
||||||
if (mGame >= EGame::EchoesDemo && !InAtomicStruct)
|
if (mGame >= EGame::EchoesDemo && !InAtomicStruct)
|
||||||
{
|
{
|
||||||
|
@ -197,7 +196,7 @@ void CScriptCooker::WriteProperty(IOutputStream& rOut, IProperty* pProperty, boo
|
||||||
}
|
}
|
||||||
|
|
||||||
for (uint32 PropertyIdx = 0; PropertyIdx < PropertiesToWrite.size(); PropertyIdx++)
|
for (uint32 PropertyIdx = 0; PropertyIdx < PropertiesToWrite.size(); PropertyIdx++)
|
||||||
WriteProperty(rOut, PropertiesToWrite[PropertyIdx], pStruct->IsAtomic());
|
WriteProperty(rOut, PropertiesToWrite[PropertyIdx], pData, pStruct->IsAtomic());
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -208,15 +207,11 @@ void CScriptCooker::WriteProperty(IOutputStream& rOut, IProperty* pProperty, boo
|
||||||
uint32 Count = pArray->ArrayCount(pData);
|
uint32 Count = pArray->ArrayCount(pData);
|
||||||
rOut.WriteLong(Count);
|
rOut.WriteLong(Count);
|
||||||
|
|
||||||
void* pOldItemData = mpArrayItemData;
|
|
||||||
|
|
||||||
for (uint32 ElementIdx = 0; ElementIdx < pArray->ArrayCount(pData); ElementIdx++)
|
for (uint32 ElementIdx = 0; ElementIdx < pArray->ArrayCount(pData); ElementIdx++)
|
||||||
{
|
{
|
||||||
mpArrayItemData = pArray->ItemPointer(pData, ElementIdx);
|
WriteProperty(rOut, pArray->ItemArchetype(), pArray->ItemPointer(pData, ElementIdx), true);
|
||||||
WriteProperty(rOut, pArray->ItemArchetype(), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mpArrayItemData = pOldItemData;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,7 +226,6 @@ void CScriptCooker::WriteProperty(IOutputStream& rOut, IProperty* pProperty, boo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ************ PUBLIC ************
|
|
||||||
void CScriptCooker::WriteInstance(IOutputStream& rOut, CScriptObject *pInstance)
|
void CScriptCooker::WriteInstance(IOutputStream& rOut, CScriptObject *pInstance)
|
||||||
{
|
{
|
||||||
ASSERT(pInstance->Area()->Game() == mGame);
|
ASSERT(pInstance->Area()->Game() == mGame);
|
||||||
|
@ -261,8 +255,7 @@ void CScriptCooker::WriteInstance(IOutputStream& rOut, CScriptObject *pInstance)
|
||||||
rOut.WriteLong(pLink->ReceiverID());
|
rOut.WriteLong(pLink->ReceiverID());
|
||||||
}
|
}
|
||||||
|
|
||||||
mpObject = pInstance;
|
WriteProperty(rOut, pInstance->Template()->Properties(), pInstance->PropertyData(), false);
|
||||||
WriteProperty(rOut, pInstance->Template()->Properties(), false);
|
|
||||||
uint32 InstanceEnd = rOut.Tell();
|
uint32 InstanceEnd = rOut.Tell();
|
||||||
|
|
||||||
rOut.Seek(SizeOffset, SEEK_SET);
|
rOut.Seek(SizeOffset, SEEK_SET);
|
||||||
|
|
|
@ -10,21 +10,16 @@
|
||||||
class CScriptCooker
|
class CScriptCooker
|
||||||
{
|
{
|
||||||
EGame mGame;
|
EGame mGame;
|
||||||
CScriptObject* mpObject;
|
|
||||||
void* mpArrayItemData;
|
|
||||||
std::vector<CScriptObject*> mGeneratedObjects;
|
std::vector<CScriptObject*> mGeneratedObjects;
|
||||||
bool mWriteGeneratedSeparately;
|
bool mWriteGeneratedSeparately;
|
||||||
|
|
||||||
void WriteProperty(IOutputStream& rOut, IProperty* pProperty, bool InAtomicStruct);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CScriptCooker(EGame Game, bool WriteGeneratedObjectsSeparately = true)
|
CScriptCooker(EGame Game, bool WriteGeneratedObjectsSeparately = true)
|
||||||
: mGame(Game)
|
: mGame(Game)
|
||||||
, mpObject(nullptr)
|
|
||||||
, mpArrayItemData(nullptr)
|
|
||||||
, mWriteGeneratedSeparately(WriteGeneratedObjectsSeparately && mGame >= EGame::EchoesDemo)
|
, mWriteGeneratedSeparately(WriteGeneratedObjectsSeparately && mGame >= EGame::EchoesDemo)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
void WriteProperty(IOutputStream& rOut, IProperty* pProperty, void* pData, bool InAtomicStruct);
|
||||||
void WriteInstance(IOutputStream& rOut, CScriptObject *pInstance);
|
void WriteInstance(IOutputStream& rOut, CScriptObject *pInstance);
|
||||||
void WriteLayer(IOutputStream& rOut, CScriptLayer *pLayer);
|
void WriteLayer(IOutputStream& rOut, CScriptLayer *pLayer);
|
||||||
void WriteGeneratedLayer(IOutputStream& rOut);
|
void WriteGeneratedLayer(IOutputStream& rOut);
|
||||||
|
|
|
@ -0,0 +1,328 @@
|
||||||
|
#include "CStringCooker.h"
|
||||||
|
#include <Common/NBasics.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
void CStringCooker::WritePrimeDemoSTRG(IOutputStream& STRG)
|
||||||
|
{
|
||||||
|
uint StartOffset = STRG.Tell();
|
||||||
|
uint NumStrings = mpStringTable->NumStrings();
|
||||||
|
|
||||||
|
// Start writing the file...
|
||||||
|
STRG.WriteLong(0); // Dummy file size
|
||||||
|
uint TableStart = STRG.Tell();
|
||||||
|
STRG.WriteLong(NumStrings);
|
||||||
|
|
||||||
|
// Dummy string offsets
|
||||||
|
for (uint StringIdx = 0; StringIdx < NumStrings; StringIdx++)
|
||||||
|
STRG.WriteLong(0);
|
||||||
|
|
||||||
|
// Write strings
|
||||||
|
std::vector<uint> StringOffsets(NumStrings);
|
||||||
|
|
||||||
|
for (uint StringIdx = 0; StringIdx < NumStrings; StringIdx++)
|
||||||
|
{
|
||||||
|
StringOffsets[StringIdx] = STRG.Tell() - TableStart;
|
||||||
|
STRG.Write16String( mpStringTable->GetString(ELanguage::English, StringIdx).ToUTF16() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in offsets
|
||||||
|
uint FileSize = STRG.Tell() - StartOffset;
|
||||||
|
STRG.GoTo(StartOffset);
|
||||||
|
STRG.WriteLong(FileSize);
|
||||||
|
STRG.Skip(4);
|
||||||
|
|
||||||
|
for (uint StringIdx = 0; StringIdx < NumStrings; StringIdx++)
|
||||||
|
STRG.WriteLong( StringOffsets[StringIdx] );
|
||||||
|
}
|
||||||
|
|
||||||
|
void CStringCooker::WritePrimeSTRG(IOutputStream& STRG)
|
||||||
|
{
|
||||||
|
// Magic/Version
|
||||||
|
STRG.WriteLong( 0x87654321 );
|
||||||
|
STRG.WriteLong( mpStringTable->Game() >= EGame::EchoesDemo ? 1 : 0 );
|
||||||
|
STRG.WriteLong( mpStringTable->NumLanguages() );
|
||||||
|
STRG.WriteLong( mpStringTable->NumStrings() );
|
||||||
|
|
||||||
|
// Language info
|
||||||
|
uint LanguagesStart = STRG.Tell();
|
||||||
|
|
||||||
|
for (uint LanguageIdx = 0; LanguageIdx < mpStringTable->NumLanguages(); LanguageIdx++)
|
||||||
|
{
|
||||||
|
const CStringTable::SLanguageData& kLanguage = mpStringTable->mLanguages[LanguageIdx];
|
||||||
|
STRG.WriteLong( (uint) kLanguage.Language );
|
||||||
|
STRG.WriteLong( 0 ); // Dummy offset
|
||||||
|
|
||||||
|
if ( mpStringTable->Game() >= EGame::EchoesDemo )
|
||||||
|
{
|
||||||
|
STRG.WriteLong( 0 ); // Dummy size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name Table
|
||||||
|
if ( mpStringTable->Game() >= EGame::EchoesDemo )
|
||||||
|
{
|
||||||
|
WriteNameTable(STRG);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings
|
||||||
|
uint StringDataStart = STRG.Tell();
|
||||||
|
std::vector<uint> LanguageOffsets( mpStringTable->NumLanguages() );
|
||||||
|
std::vector<uint> LanguageSizes( mpStringTable->NumLanguages() );
|
||||||
|
|
||||||
|
for (uint LanguageIdx = 0; LanguageIdx < mpStringTable->NumLanguages(); LanguageIdx++)
|
||||||
|
{
|
||||||
|
const CStringTable::SLanguageData& kLanguage = mpStringTable->mLanguages[LanguageIdx];
|
||||||
|
|
||||||
|
uint LanguageStart = STRG.Tell();
|
||||||
|
LanguageOffsets[LanguageIdx] = LanguageStart - StringDataStart;
|
||||||
|
|
||||||
|
if ( mpStringTable->Game() == EGame::Prime )
|
||||||
|
{
|
||||||
|
STRG.WriteLong( 0 ); // Dummy size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill dummy string offsets
|
||||||
|
uint StringOffsetBase = STRG.Tell();
|
||||||
|
|
||||||
|
for (uint StringIdx = 0; StringIdx < mpStringTable->NumStrings(); StringIdx++)
|
||||||
|
{
|
||||||
|
STRG.WriteLong( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write strings
|
||||||
|
std::vector<uint> StringOffsets( mpStringTable->NumStrings() );
|
||||||
|
|
||||||
|
for (uint StringIdx = 0; StringIdx < mpStringTable->NumStrings(); StringIdx++)
|
||||||
|
{
|
||||||
|
StringOffsets[StringIdx] = STRG.Tell() - StringOffsetBase;
|
||||||
|
STRG.Write16String( kLanguage.Strings[StringIdx].String.ToUTF16() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go back and fill in size/offsets
|
||||||
|
uint LanguageEnd = STRG.Tell();
|
||||||
|
LanguageSizes[LanguageIdx] = LanguageEnd - StringOffsetBase;
|
||||||
|
STRG.GoTo(LanguageStart);
|
||||||
|
|
||||||
|
if ( mpStringTable->Game() == EGame::Prime )
|
||||||
|
{
|
||||||
|
STRG.WriteLong( LanguageSizes[LanguageIdx] );
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint StringIdx = 0; StringIdx < mpStringTable->NumStrings(); StringIdx++)
|
||||||
|
{
|
||||||
|
STRG.WriteLong( StringOffsets[StringIdx] );
|
||||||
|
}
|
||||||
|
|
||||||
|
STRG.GoTo(LanguageEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint STRGEnd = STRG.Tell();
|
||||||
|
|
||||||
|
// Fill in missing language data
|
||||||
|
STRG.GoTo(LanguagesStart);
|
||||||
|
|
||||||
|
for (uint LanguageIdx = 0; LanguageIdx < mpStringTable->NumLanguages(); LanguageIdx++)
|
||||||
|
{
|
||||||
|
STRG.Skip(4); // Skip language ID
|
||||||
|
STRG.WriteLong( LanguageOffsets[LanguageIdx] );
|
||||||
|
|
||||||
|
if ( mpStringTable->Game() >= EGame::EchoesDemo )
|
||||||
|
{
|
||||||
|
STRG.WriteLong( LanguageSizes[LanguageIdx] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
STRG.GoTo(STRGEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CStringCooker::WriteCorruptionSTRG(IOutputStream& STRG)
|
||||||
|
{
|
||||||
|
// Magic/Version
|
||||||
|
STRG.WriteLong( 0x87654321 );
|
||||||
|
STRG.WriteLong( 3 );
|
||||||
|
STRG.WriteLong( mpStringTable->NumLanguages() );
|
||||||
|
STRG.WriteLong( mpStringTable->NumStrings() );
|
||||||
|
|
||||||
|
// Name Table
|
||||||
|
WriteNameTable(STRG);
|
||||||
|
|
||||||
|
// Create some structures before continuing...
|
||||||
|
// In MP3 and DKCR, if a string has not been localized, then the English text
|
||||||
|
// is reused, instead of duplicating the string data like MP1 and MP2 would have.
|
||||||
|
struct SCookedLanguageData
|
||||||
|
{
|
||||||
|
ELanguage Language;
|
||||||
|
std::vector<uint> StringOffsets;
|
||||||
|
uint TotalSize;
|
||||||
|
};
|
||||||
|
std::vector<SCookedLanguageData> CookedLanguageData( mpStringTable->NumLanguages() );
|
||||||
|
|
||||||
|
for (uint LanguageIdx = 0; LanguageIdx < mpStringTable->NumLanguages(); LanguageIdx++)
|
||||||
|
{
|
||||||
|
const CStringTable::SLanguageData& kLanguageData = mpStringTable->mLanguages[LanguageIdx];
|
||||||
|
|
||||||
|
SCookedLanguageData& CookedData = CookedLanguageData[LanguageIdx];
|
||||||
|
CookedData.Language = kLanguageData.Language;
|
||||||
|
CookedData.StringOffsets.resize( mpStringTable->NumStrings() );
|
||||||
|
CookedData.TotalSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Language IDs
|
||||||
|
for (uint LanguageIdx = 0; LanguageIdx < mpStringTable->NumLanguages(); LanguageIdx++)
|
||||||
|
{
|
||||||
|
STRG.WriteLong( (uint) CookedLanguageData[LanguageIdx].Language );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Language Info
|
||||||
|
uint LanguageInfoStart = STRG.Tell();
|
||||||
|
|
||||||
|
for (uint LanguageIdx = 0; LanguageIdx < mpStringTable->NumLanguages(); LanguageIdx++)
|
||||||
|
{
|
||||||
|
// Fill language size/offsets with dummy data...
|
||||||
|
STRG.WriteLong( 0 );
|
||||||
|
|
||||||
|
for (uint StringIdx = 0; StringIdx < mpStringTable->NumStrings(); StringIdx++)
|
||||||
|
STRG.WriteLong( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some of the following code assumes that language 0 is English.
|
||||||
|
ASSERT( mpStringTable->mLanguages[0].Language == ELanguage::English );
|
||||||
|
|
||||||
|
// Strings
|
||||||
|
uint StringsStart = STRG.Tell();
|
||||||
|
|
||||||
|
for (uint StringIdx = 0; StringIdx < mpStringTable->NumStrings(); StringIdx++)
|
||||||
|
{
|
||||||
|
for (uint LanguageIdx = 0; LanguageIdx < mpStringTable->NumLanguages(); LanguageIdx++)
|
||||||
|
{
|
||||||
|
const CStringTable::SLanguageData& kLanguageData = mpStringTable->mLanguages[LanguageIdx];
|
||||||
|
const CStringTable::SStringData& kStringData = kLanguageData.Strings[StringIdx];
|
||||||
|
SCookedLanguageData& CookedData = CookedLanguageData[LanguageIdx];
|
||||||
|
|
||||||
|
// If the "localized" flag is disabled, then we will not write this string. Instead, it will
|
||||||
|
// reuse the offset for the English text.
|
||||||
|
if (LanguageIdx == 0 || kStringData.IsLocalized)
|
||||||
|
{
|
||||||
|
CookedData.StringOffsets[StringIdx] = STRG.Tell() - StringsStart;
|
||||||
|
CookedData.TotalSize += kStringData.String.Size() + 1; // +1 for terminating zero
|
||||||
|
STRG.WriteLong( kStringData.String.Size() + 1 );
|
||||||
|
STRG.WriteString( kStringData.String );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CookedData.StringOffsets[StringIdx] = CookedLanguageData[0].StringOffsets[StringIdx];
|
||||||
|
CookedData.TotalSize += mpStringTable->mLanguages[0].Strings[StringIdx].String.Size() + 1; // +1 for terminating zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint STRGEnd = STRG.Tell();
|
||||||
|
|
||||||
|
// Fill in missing language data
|
||||||
|
STRG.GoTo(LanguageInfoStart);
|
||||||
|
|
||||||
|
for (uint LanguageIdx = 0; LanguageIdx < mpStringTable->NumLanguages(); LanguageIdx++)
|
||||||
|
{
|
||||||
|
const SCookedLanguageData& kCookedData = CookedLanguageData[LanguageIdx];
|
||||||
|
STRG.WriteLong( kCookedData.TotalSize );
|
||||||
|
|
||||||
|
for (uint StringIdx = 0; StringIdx < mpStringTable->NumStrings(); StringIdx++)
|
||||||
|
STRG.WriteLong( kCookedData.StringOffsets[StringIdx] );
|
||||||
|
}
|
||||||
|
|
||||||
|
STRG.GoTo(STRGEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CStringCooker::WriteNameTable(IOutputStream& STRG)
|
||||||
|
{
|
||||||
|
// Build a list of name entries to put in the map
|
||||||
|
struct SNameEntry
|
||||||
|
{
|
||||||
|
uint Offset;
|
||||||
|
uint Index;
|
||||||
|
TString Name;
|
||||||
|
};
|
||||||
|
std::vector<SNameEntry> NameEntries;
|
||||||
|
|
||||||
|
for (uint StringIdx = 0; StringIdx < mpStringTable->NumStrings(); StringIdx++)
|
||||||
|
{
|
||||||
|
SNameEntry Entry;
|
||||||
|
Entry.Offset = 0;
|
||||||
|
Entry.Index = StringIdx;
|
||||||
|
Entry.Name = mpStringTable->StringNameByIndex(StringIdx);
|
||||||
|
|
||||||
|
if (!Entry.Name.IsEmpty())
|
||||||
|
{
|
||||||
|
NameEntries.push_back(Entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stable_sort( NameEntries.begin(), NameEntries.end(), [](const SNameEntry& kLHS, const SNameEntry& kRHS) -> bool {
|
||||||
|
return kLHS.Name < kRHS.Name;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Write out name entries
|
||||||
|
uint NameTableStart = STRG.Tell();
|
||||||
|
STRG.WriteLong( NameEntries.size() );
|
||||||
|
STRG.WriteLong( 0 ); // Dummy name table size
|
||||||
|
uint NameTableOffsetsStart = STRG.Tell();
|
||||||
|
|
||||||
|
for (uint NameIdx = 0; NameIdx < NameEntries.size(); NameIdx++)
|
||||||
|
{
|
||||||
|
STRG.WriteLong( 0 ); // Dummy name offset
|
||||||
|
STRG.WriteLong( NameEntries[NameIdx].Index );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write out names
|
||||||
|
std::vector<uint> NameOffsets( NameEntries.size() );
|
||||||
|
|
||||||
|
for (uint NameIdx = 0; NameIdx < NameEntries.size(); NameIdx++)
|
||||||
|
{
|
||||||
|
NameOffsets[NameIdx] = STRG.Tell() - NameTableOffsetsStart;
|
||||||
|
STRG.WriteString( NameEntries[NameIdx].Name );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill out sizes and offsets
|
||||||
|
uint NameTableEnd = STRG.Tell();
|
||||||
|
uint NameTableSize = NameTableEnd - NameTableOffsetsStart;
|
||||||
|
|
||||||
|
STRG.GoTo(NameTableStart);
|
||||||
|
STRG.Skip(4);
|
||||||
|
STRG.WriteLong(NameTableSize);
|
||||||
|
|
||||||
|
for (uint NameIdx = 0; NameIdx < NameEntries.size(); NameIdx++)
|
||||||
|
{
|
||||||
|
STRG.WriteLong( NameOffsets[NameIdx] );
|
||||||
|
STRG.Skip(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
STRG.GoTo(NameTableEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Static entry point */
|
||||||
|
bool CStringCooker::CookSTRG(CStringTable* pStringTable, IOutputStream& STRG)
|
||||||
|
{
|
||||||
|
CStringCooker Cooker(pStringTable);
|
||||||
|
|
||||||
|
switch (pStringTable->Game())
|
||||||
|
{
|
||||||
|
case EGame::PrimeDemo:
|
||||||
|
Cooker.WritePrimeDemoSTRG(STRG);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case EGame::Prime:
|
||||||
|
case EGame::EchoesDemo:
|
||||||
|
case EGame::Echoes:
|
||||||
|
case EGame::CorruptionProto:
|
||||||
|
Cooker.WritePrimeSTRG(STRG);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case EGame::Corruption:
|
||||||
|
case EGame::DKCReturns:
|
||||||
|
Cooker.WriteCorruptionSTRG(STRG);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef CSTRINGCOOKER_H
|
||||||
|
#define CSTRINGCOOKER_H
|
||||||
|
|
||||||
|
#include "Core/Resource/TResPtr.h"
|
||||||
|
#include "Core/Resource/StringTable/CStringTable.h"
|
||||||
|
|
||||||
|
/** Cooker class for writing game-compatible STRG assets */
|
||||||
|
class CStringCooker
|
||||||
|
{
|
||||||
|
TResPtr<CStringTable> mpStringTable;
|
||||||
|
|
||||||
|
CStringCooker(CStringTable* pStringTable)
|
||||||
|
: mpStringTable(pStringTable) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void WritePrimeDemoSTRG(IOutputStream& STRG);
|
||||||
|
void WritePrimeSTRG(IOutputStream& STRG);
|
||||||
|
void WriteCorruptionSTRG(IOutputStream& STRG);
|
||||||
|
void WriteNameTable(IOutputStream& STRG);
|
||||||
|
|
||||||
|
/** Static entry point */
|
||||||
|
static bool CookSTRG(CStringTable* pStringTable, IOutputStream& STRG);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CSTRINGCOOKER_H
|
|
@ -66,7 +66,7 @@ enum class EResourceType
|
||||||
StringList,
|
StringList,
|
||||||
StringTable,
|
StringTable,
|
||||||
Texture,
|
Texture,
|
||||||
Tweak,
|
Tweaks,
|
||||||
UserEvaluatorData,
|
UserEvaluatorData,
|
||||||
Video,
|
Video,
|
||||||
World,
|
World,
|
||||||
|
|
|
@ -81,7 +81,7 @@ void CAnimationLoader::ReadUncompressedANIM()
|
||||||
// Echoes only - rotation channel indices
|
// Echoes only - rotation channel indices
|
||||||
std::vector<uint8> RotationIndices;
|
std::vector<uint8> RotationIndices;
|
||||||
|
|
||||||
if (mGame == EGame::Echoes)
|
if (mGame >= EGame::EchoesDemo)
|
||||||
{
|
{
|
||||||
uint32 NumRotationIndices = mpInput->ReadLong();
|
uint32 NumRotationIndices = mpInput->ReadLong();
|
||||||
RotationIndices.resize(NumRotationIndices);
|
RotationIndices.resize(NumRotationIndices);
|
||||||
|
@ -125,7 +125,7 @@ void CAnimationLoader::ReadUncompressedANIM()
|
||||||
// Echoes only - scale channel indices
|
// Echoes only - scale channel indices
|
||||||
std::vector<uint8> ScaleIndices;
|
std::vector<uint8> ScaleIndices;
|
||||||
|
|
||||||
if (mGame == EGame::Echoes)
|
if (mGame >= EGame::EchoesDemo)
|
||||||
{
|
{
|
||||||
uint32 NumScaleIndices = mpInput->ReadLong();
|
uint32 NumScaleIndices = mpInput->ReadLong();
|
||||||
ScaleIndices.resize(NumScaleIndices);
|
ScaleIndices.resize(NumScaleIndices);
|
||||||
|
@ -161,7 +161,7 @@ void CAnimationLoader::ReadUncompressedANIM()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read bone transforms
|
// Read bone transforms
|
||||||
if (mGame == EGame::Echoes)
|
if (mGame >= EGame::EchoesDemo)
|
||||||
{
|
{
|
||||||
mpInput->Seek(0x4, SEEK_CUR); // Skipping scale key count
|
mpInput->Seek(0x4, SEEK_CUR); // Skipping scale key count
|
||||||
mpAnim->mScaleChannels.resize(NumScaleChannels);
|
mpAnim->mScaleChannels.resize(NumScaleChannels);
|
||||||
|
@ -208,23 +208,24 @@ void CAnimationLoader::ReadCompressedANIM()
|
||||||
// Header
|
// Header
|
||||||
mpInput->Seek(0x4, SEEK_CUR); // Skip alloc size
|
mpInput->Seek(0x4, SEEK_CUR); // Skip alloc size
|
||||||
|
|
||||||
if (mGame == EGame::Invalid)
|
// Version check
|
||||||
mGame = (mpInput->PeekShort() == 0x0101 ? EGame::Echoes : EGame::Prime);
|
mGame = (mpInput->PeekShort() == 0x0101 ? EGame::Echoes : EGame::Prime);
|
||||||
|
|
||||||
if (mGame == EGame::Prime)
|
// Check the ANIM resource's game instead of the version check we just determined.
|
||||||
|
// The Echoes demo has some ANIMs that use MP1's format, but don't have the EVNT reference.
|
||||||
|
if (mpAnim->Game() <= EGame::Prime)
|
||||||
{
|
{
|
||||||
mpAnim->mpEventData = gpResourceStore->LoadResource<CAnimEventData>(mpInput->ReadLong());
|
mpAnim->mpEventData = gpResourceStore->LoadResource<CAnimEventData>(mpInput->ReadLong());
|
||||||
mpInput->Seek(0x4, SEEK_CUR); // Skip unknown
|
|
||||||
}
|
}
|
||||||
else mpInput->Seek(0x2, SEEK_CUR); // Skip unknowns
|
|
||||||
|
|
||||||
|
mpInput->Seek(mGame <= EGame::Prime ? 4 : 2, SEEK_CUR); // Skip unknowns
|
||||||
mpAnim->mDuration = mpInput->ReadFloat();
|
mpAnim->mDuration = mpInput->ReadFloat();
|
||||||
mpAnim->mTickInterval = mpInput->ReadFloat();
|
mpAnim->mTickInterval = mpInput->ReadFloat();
|
||||||
mpInput->Seek(0x8, SEEK_CUR); // Skip two unknown values
|
mpInput->Seek(0x8, SEEK_CUR); // Skip two unknown values
|
||||||
|
|
||||||
mRotationDivisor = mpInput->ReadLong();
|
mRotationDivisor = mpInput->ReadLong();
|
||||||
mTranslationMultiplier = mpInput->ReadFloat();
|
mTranslationMultiplier = mpInput->ReadFloat();
|
||||||
if (mGame == EGame::Echoes) mScaleMultiplier = mpInput->ReadFloat();
|
if (mGame >= EGame::EchoesDemo) mScaleMultiplier = mpInput->ReadFloat();
|
||||||
uint32 NumBoneChannels = mpInput->ReadLong();
|
uint32 NumBoneChannels = mpInput->ReadLong();
|
||||||
mpInput->Seek(0x4, SEEK_CUR); // Skip unknown value
|
mpInput->Seek(0x4, SEEK_CUR); // Skip unknown value
|
||||||
|
|
||||||
|
@ -284,7 +285,7 @@ void CAnimationLoader::ReadCompressedANIM()
|
||||||
// Read scale parameters
|
// Read scale parameters
|
||||||
uint8 ScaleIdx = 0xFF;
|
uint8 ScaleIdx = 0xFF;
|
||||||
|
|
||||||
if (mGame == EGame::Echoes)
|
if (mGame >= EGame::EchoesDemo)
|
||||||
{
|
{
|
||||||
rChan.NumScaleKeys = mpInput->ReadShort();
|
rChan.NumScaleKeys = mpInput->ReadShort();
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
#include "CUnsupportedParticleLoader.h"
|
#include "CUnsupportedParticleLoader.h"
|
||||||
#include "CWorldLoader.h"
|
#include "CWorldLoader.h"
|
||||||
|
|
||||||
|
#include "Core/Tweaks/CTweakLoader.h"
|
||||||
|
|
||||||
#include "Core/Resource/Resources.h"
|
#include "Core/Resource/Resources.h"
|
||||||
|
|
||||||
// Static helper class to allow spawning resources based on an EResType
|
// Static helper class to allow spawning resources based on an EResType
|
||||||
|
@ -97,6 +99,7 @@ public:
|
||||||
case EResourceType::StringList: pRes = CAudioGroupLoader::LoadSTLC(rInput, pEntry); break;
|
case EResourceType::StringList: pRes = CAudioGroupLoader::LoadSTLC(rInput, pEntry); break;
|
||||||
case EResourceType::StringTable: pRes = CStringLoader::LoadSTRG(rInput, pEntry); break;
|
case EResourceType::StringTable: pRes = CStringLoader::LoadSTRG(rInput, pEntry); break;
|
||||||
case EResourceType::Texture: pRes = CTextureDecoder::LoadTXTR(rInput, pEntry); break;
|
case EResourceType::Texture: pRes = CTextureDecoder::LoadTXTR(rInput, pEntry); break;
|
||||||
|
case EResourceType::Tweaks: pRes = CTweakLoader::LoadCTWK(rInput, pEntry); break;
|
||||||
case EResourceType::World: pRes = CWorldLoader::LoadMLVL(rInput, pEntry); break;
|
case EResourceType::World: pRes = CWorldLoader::LoadMLVL(rInput, pEntry); break;
|
||||||
|
|
||||||
case EResourceType::StateMachine:
|
case EResourceType::StateMachine:
|
||||||
|
|
|
@ -1,306 +1,83 @@
|
||||||
#include "CScanLoader.h"
|
#include "CScanLoader.h"
|
||||||
#include "Core/GameProject/CResourceStore.h"
|
#include "Core/GameProject/CResourceStore.h"
|
||||||
|
#include "CScriptLoader.h"
|
||||||
#include <Common/Log.h>
|
#include <Common/Log.h>
|
||||||
|
|
||||||
CScanLoader::CScanLoader()
|
CScan* CScanLoader::LoadScanMP1(IInputStream& SCAN, CResourceEntry* pEntry)
|
||||||
{
|
{
|
||||||
}
|
// Validate magic
|
||||||
|
uint Magic = SCAN.ReadLong();
|
||||||
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::Scan);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Magic != 0x0BADBEEF)
|
if (Magic != 0x0BADBEEF)
|
||||||
{
|
{
|
||||||
errorf("%s: Invalid SCAN magic: 0x%08X", *rSCAN.GetSourceString(), Magic);
|
errorf("Invalid magic in SCAN asset: 0x%08X", Magic);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FileVersion != 5)
|
// 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
|
||||||
errorf("%s: Unsupported SCAN version: 0x%X", *rSCAN.GetSourceString(), FileVersion);
|
// way as a normal file format... however, since we support all games, we need to support
|
||||||
return nullptr;
|
// 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.
|
||||||
// MP1 SCAN - read the file!
|
mpScan = new CScan(pEntry);
|
||||||
CScanLoader Loader;
|
CScriptLoader::LoadStructData(SCAN, mpScan->ScanData());
|
||||||
Loader.mVersion = EGame::Prime;
|
return mpScan;
|
||||||
Loader.mpScan = new CScan(pEntry);
|
}
|
||||||
Loader.mpEntry = pEntry;
|
|
||||||
return Loader.LoadScanMP1(rSCAN);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,16 @@
|
||||||
#ifndef CSCANLOADER_H
|
#ifndef CSCANLOADER_H
|
||||||
#define CSCANLOADER_H
|
#define CSCANLOADER_H
|
||||||
|
|
||||||
#include "Core/Resource/CScan.h"
|
#include "Core/Resource/Scan/CScan.h"
|
||||||
#include <Common/EGame.h>
|
#include <Common/EGame.h>
|
||||||
|
|
||||||
class CScanLoader
|
class CScanLoader
|
||||||
{
|
{
|
||||||
TResPtr<CScan> mpScan;
|
TResPtr<CScan> mpScan;
|
||||||
CResourceEntry *mpEntry;
|
|
||||||
EGame mVersion;
|
|
||||||
|
|
||||||
CScanLoader();
|
CScanLoader() {}
|
||||||
CScan* LoadScanMP1(IInputStream& rSCAN);
|
CScan* LoadScanMP1(IInputStream& SCAN, CResourceEntry* pEntry);
|
||||||
CScan* LoadScanMP2(IInputStream& rSCAN);
|
CScan* LoadScanMP2(IInputStream& SCAN, CResourceEntry* pEntry);
|
||||||
void LoadParamsMP2(IInputStream& rSCAN, uint16 NumProperties);
|
|
||||||
void LoadParamsMP3(IInputStream& rSCAN, uint16 NumProperties);
|
|
||||||
void LoadScanInfoSecondaryModel(IInputStream& rSCAN, CScan::SScanInfoSecondaryModel& rSecondaryModel);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static CScan* LoadSCAN(IInputStream& rSCAN, CResourceEntry *pEntry);
|
static CScan* LoadSCAN(IInputStream& rSCAN, CResourceEntry *pEntry);
|
||||||
|
|
|
@ -15,13 +15,13 @@
|
||||||
|
|
||||||
CScriptLoader::CScriptLoader()
|
CScriptLoader::CScriptLoader()
|
||||||
: mpObj(nullptr)
|
: mpObj(nullptr)
|
||||||
, mpArrayItemData(nullptr)
|
, mpCurrentData(nullptr)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void CScriptLoader::ReadProperty(IProperty *pProp, uint32 Size, IInputStream& rSCLY)
|
void CScriptLoader::ReadProperty(IProperty *pProp, uint32 Size, IInputStream& rSCLY)
|
||||||
{
|
{
|
||||||
void* pData = (mpArrayItemData ? mpArrayItemData : mpObj->mPropertyData.data());
|
void* pData = (mpCurrentData ? mpCurrentData : mpObj->mPropertyData.data());
|
||||||
|
|
||||||
switch (pProp->Type())
|
switch (pProp->Type())
|
||||||
{
|
{
|
||||||
|
@ -151,7 +151,7 @@ void CScriptLoader::ReadProperty(IProperty *pProp, uint32 Size, IInputStream& rS
|
||||||
#if VALIDATE_PROPERTY_VALUES
|
#if VALIDATE_PROPERTY_VALUES
|
||||||
CAssetID ID = pAsset->ValueRef(pData);
|
CAssetID ID = pAsset->ValueRef(pData);
|
||||||
|
|
||||||
if (ID.IsValid())
|
if (ID.IsValid() && gpResourceStore)
|
||||||
{
|
{
|
||||||
CResourceEntry *pEntry = gpResourceStore->FindEntry(ID);
|
CResourceEntry *pEntry = gpResourceStore->FindEntry(ID);
|
||||||
|
|
||||||
|
@ -237,7 +237,7 @@ void CScriptLoader::ReadProperty(IProperty *pProp, uint32 Size, IInputStream& rS
|
||||||
int Count = rSCLY.ReadLong();
|
int Count = rSCLY.ReadLong();
|
||||||
|
|
||||||
pArray->Resize(pData, Count);
|
pArray->Resize(pData, Count);
|
||||||
void* pOldArrayItemData = mpArrayItemData;
|
void* pOldData = mpCurrentData;
|
||||||
|
|
||||||
// Make sure the array archetype is atomic... non-atomic array archetypes is not supported
|
// 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
|
// because arrays can only have one possible archetype so having property IDs here wouldn't make sense
|
||||||
|
@ -257,11 +257,11 @@ void CScriptLoader::ReadProperty(IProperty *pProp, uint32 Size, IInputStream& rS
|
||||||
* migrated to Sequence properties eventually, so there isn't really any good reason to spend a lot of effort refactoring
|
* 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
|
* things to make this cleaner
|
||||||
*/
|
*/
|
||||||
mpArrayItemData = pArray->ItemPointer(pData, ElementIdx);
|
mpCurrentData = pArray->ItemPointer(pData, ElementIdx);
|
||||||
ReadProperty(pArray->ItemArchetype(), 0, rSCLY);
|
ReadProperty(pArray->ItemArchetype(), 0, rSCLY);
|
||||||
}
|
}
|
||||||
|
|
||||||
mpArrayItemData = pOldArrayItemData;
|
mpCurrentData = pOldData;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -505,3 +505,20 @@ CScriptObject* CScriptLoader::LoadInstance(IInputStream& rSCLY, CGameArea *pArea
|
||||||
else
|
else
|
||||||
return Loader.LoadObjectMP2(rSCLY);
|
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());
|
||||||
|
}
|
||||||
|
|
|
@ -15,8 +15,8 @@ class CScriptLoader
|
||||||
CGameArea* mpArea;
|
CGameArea* mpArea;
|
||||||
CGameTemplate *mpGameTemplate;
|
CGameTemplate *mpGameTemplate;
|
||||||
|
|
||||||
// Current array item pointer
|
// Current data pointer
|
||||||
void* mpArrayItemData;
|
void* mpCurrentData;
|
||||||
|
|
||||||
CScriptLoader();
|
CScriptLoader();
|
||||||
void ReadProperty(IProperty* pProp, uint32 Size, IInputStream& rSCLY);
|
void ReadProperty(IProperty* pProp, uint32 Size, IInputStream& rSCLY);
|
||||||
|
@ -32,6 +32,7 @@ class CScriptLoader
|
||||||
public:
|
public:
|
||||||
static CScriptLayer* LoadLayer(IInputStream& rSCLY, CGameArea *pArea, EGame Version);
|
static CScriptLayer* LoadLayer(IInputStream& rSCLY, CGameArea *pArea, EGame Version);
|
||||||
static CScriptObject* LoadInstance(IInputStream& rSCLY, CGameArea *pArea, CScriptLayer *pLayer, EGame Version, bool ForceReturnsFormat);
|
static CScriptObject* LoadInstance(IInputStream& rSCLY, CGameArea *pArea, CScriptLayer *pLayer, EGame Version, bool ForceReturnsFormat);
|
||||||
|
static void LoadStructData(IInputStream& rInput, CStructRef InStruct);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CSCRIPTLOADER_H
|
#endif // CSCRIPTLOADER_H
|
||||||
|
|
|
@ -1,193 +1,234 @@
|
||||||
#include "CStringLoader.h"
|
#include "CStringLoader.h"
|
||||||
#include <Common/Log.h>
|
#include <Common/Log.h>
|
||||||
|
#include <Common/Math/MathUtil.h>
|
||||||
|
|
||||||
void CStringLoader::LoadPrimeDemoSTRG(IInputStream& rSTRG)
|
void CStringLoader::LoadPrimeDemoSTRG(IInputStream& STRG)
|
||||||
{
|
{
|
||||||
// This function starts at 0x4 in the file - right after the size
|
// This function starts at 0x4 in the file - right after the size
|
||||||
// This STRG version only supports one language per file
|
// This STRG version only supports one language per file
|
||||||
mpStringTable->mLangTables.resize(1);
|
mpStringTable->mLanguages.resize(1);
|
||||||
CStringTable::SLangTable* Lang = &mpStringTable->mLangTables[1];
|
CStringTable::SLanguageData& Language = mpStringTable->mLanguages[0];
|
||||||
Lang->Language = "ENGL";
|
Language.Language = ELanguage::English;
|
||||||
uint32 TableStart = rSTRG.Tell();
|
uint TableStart = STRG.Tell();
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
uint32 NumStrings = rSTRG.ReadLong();
|
uint NumStrings = STRG.ReadLong();
|
||||||
Lang->Strings.resize(NumStrings);
|
Language.Strings.resize(NumStrings);
|
||||||
mpStringTable->mNumStrings = NumStrings;
|
|
||||||
|
|
||||||
// String offsets (yeah, that wasn't much of a header)
|
// String offsets (yeah, that wasn't much of a header)
|
||||||
std::vector<uint32> StringOffsets(NumStrings);
|
std::vector<uint> StringOffsets(NumStrings);
|
||||||
for (uint32 iOff = 0; iOff < StringOffsets.size(); iOff++)
|
for (uint32 OffsetIdx = 0; OffsetIdx < NumStrings; OffsetIdx++)
|
||||||
StringOffsets[iOff] = rSTRG.ReadLong();
|
StringOffsets[OffsetIdx] = STRG.ReadLong();
|
||||||
|
|
||||||
// Strings
|
// Strings
|
||||||
for (uint32 iStr = 0; iStr < NumStrings; iStr++)
|
for (uint StringIdx = 0; StringIdx < NumStrings; StringIdx++)
|
||||||
{
|
{
|
||||||
rSTRG.Seek(TableStart + StringOffsets[iStr], SEEK_SET);
|
STRG.GoTo( TableStart + StringOffsets[StringIdx] );
|
||||||
Lang->Strings[iStr] = rSTRG.ReadWString().ToUTF8();
|
Language.Strings[StringIdx].String = STRG.Read16String().ToUTF8();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStringLoader::LoadPrimeSTRG(IInputStream& rSTRG)
|
void CStringLoader::LoadPrimeSTRG(IInputStream& STRG)
|
||||||
{
|
{
|
||||||
// This function starts at 0x8 in the file, after magic/version
|
// This function starts at 0x8 in the file, after magic/version
|
||||||
// Header
|
// Header
|
||||||
uint32 NumLanguages = rSTRG.ReadLong();
|
uint NumLanguages = STRG.ReadLong();
|
||||||
uint32 NumStrings = rSTRG.ReadLong();
|
uint NumStrings = STRG.ReadLong();
|
||||||
mpStringTable->mNumStrings = NumStrings;
|
|
||||||
|
|
||||||
// Language definitions
|
// Language definitions
|
||||||
mpStringTable->mLangTables.resize(NumLanguages);
|
mpStringTable->mLanguages.resize(NumLanguages);
|
||||||
std::vector<uint32> LangOffsets(NumLanguages);
|
std::vector<uint> LanguageOffsets(NumLanguages);
|
||||||
|
|
||||||
for (uint32 iLang = 0; iLang < NumLanguages; iLang++)
|
for (uint LanguageIdx = 0; LanguageIdx < NumLanguages; LanguageIdx++)
|
||||||
{
|
{
|
||||||
mpStringTable->mLangTables[iLang].Language = CFourCC(rSTRG);
|
mpStringTable->mLanguages[LanguageIdx].Language = (ELanguage) STRG.ReadFourCC();
|
||||||
LangOffsets[iLang] = rSTRG.ReadLong();
|
LanguageOffsets[LanguageIdx] = STRG.ReadLong();
|
||||||
if (mVersion == EGame::Echoes) rSTRG.Seek(0x4, SEEK_CUR); // Skipping strings size
|
|
||||||
|
// Skip strings size in MP2
|
||||||
|
if (mVersion >= EGame::EchoesDemo)
|
||||||
|
{
|
||||||
|
STRG.Skip(4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some of the following code assumes that language 0 is English
|
||||||
|
ASSERT( mpStringTable->mLanguages[0].Language == ELanguage::English );
|
||||||
|
|
||||||
// String names
|
// String names
|
||||||
if (mVersion == EGame::Echoes)
|
if (mVersion >= EGame::EchoesDemo)
|
||||||
LoadNameTable(rSTRG);
|
{
|
||||||
|
LoadNameTable(STRG);
|
||||||
|
}
|
||||||
|
|
||||||
// Strings
|
// Strings
|
||||||
uint32 StringsStart = rSTRG.Tell();
|
uint StringsStart = STRG.Tell();
|
||||||
for (uint32 iLang = 0; iLang < NumLanguages; iLang++)
|
for (uint32 LanguageIdx = 0; LanguageIdx < NumLanguages; LanguageIdx++)
|
||||||
{
|
{
|
||||||
rSTRG.Seek(StringsStart + LangOffsets[iLang], SEEK_SET);
|
STRG.GoTo( StringsStart + LanguageOffsets[LanguageIdx] );
|
||||||
if (mVersion == EGame::Prime) rSTRG.Seek(0x4, SEEK_CUR); // Skipping strings size
|
|
||||||
|
|
||||||
uint32 LangStart = rSTRG.Tell();
|
// Skip strings size in MP1
|
||||||
CStringTable::SLangTable* pLang = &mpStringTable->mLangTables[iLang];
|
if (mVersion == EGame::Prime)
|
||||||
pLang->Strings.resize(NumStrings);
|
{
|
||||||
|
STRG.Skip(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
CStringTable::SLanguageData& Language = mpStringTable->mLanguages[LanguageIdx];
|
||||||
|
Language.Strings.resize(NumStrings);
|
||||||
|
|
||||||
// Offsets
|
// Offsets
|
||||||
std::vector<uint32> StringOffsets(NumStrings);
|
uint LanguageStart = STRG.Tell();
|
||||||
for (uint32 iOff = 0; iOff < NumStrings; iOff++)
|
std::vector<uint> StringOffsets(NumStrings);
|
||||||
StringOffsets[iOff] = rSTRG.ReadLong();
|
|
||||||
|
for (uint StringIdx = 0; StringIdx < NumStrings; StringIdx++)
|
||||||
|
{
|
||||||
|
StringOffsets[StringIdx] = LanguageStart + STRG.ReadLong();
|
||||||
|
}
|
||||||
|
|
||||||
// The actual strings
|
// The actual strings
|
||||||
for (uint32 iStr = 0; iStr < NumStrings; iStr++)
|
for (uint StringIdx = 0; StringIdx < NumStrings; StringIdx++)
|
||||||
{
|
{
|
||||||
rSTRG.Seek(LangStart + StringOffsets[iStr], SEEK_SET);
|
STRG.GoTo( StringOffsets[StringIdx] );
|
||||||
pLang->Strings[iStr] = rSTRG.ReadWString().ToUTF8();
|
TString String = STRG.Read16String().ToUTF8();
|
||||||
|
|
||||||
|
// Flag the string as localized if it is different than the English
|
||||||
|
// version of the same string.
|
||||||
|
const TString& kEnglishString = (StringIdx == 0 ? String :
|
||||||
|
mpStringTable->mLanguages[0].Strings[StringIdx].String);
|
||||||
|
|
||||||
|
bool IsLocalized = (LanguageIdx == 0 || String != kEnglishString);
|
||||||
|
|
||||||
|
Language.Strings[StringIdx].String = String;
|
||||||
|
Language.Strings[StringIdx].IsLocalized = IsLocalized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStringLoader::LoadCorruptionSTRG(IInputStream& rSTRG)
|
void CStringLoader::LoadCorruptionSTRG(IInputStream& STRG)
|
||||||
{
|
{
|
||||||
// This function starts at 0x8 in the file, after magic/version
|
// This function starts at 0x8 in the file, after magic/version
|
||||||
// Header
|
// Header
|
||||||
uint32 NumLanguages = rSTRG.ReadLong();
|
uint NumLanguages = STRG.ReadLong();
|
||||||
uint32 NumStrings = rSTRG.ReadLong();
|
uint NumStrings = STRG.ReadLong();
|
||||||
mpStringTable->mNumStrings = NumStrings;
|
|
||||||
|
|
||||||
// String names
|
// String names
|
||||||
LoadNameTable(rSTRG);
|
LoadNameTable(STRG);
|
||||||
|
|
||||||
// Language definitions
|
// Language definitions
|
||||||
mpStringTable->mLangTables.resize(NumLanguages);
|
mpStringTable->mLanguages.resize(NumLanguages);
|
||||||
std::vector<std::vector<uint32>> LangOffsets(NumLanguages);
|
std::vector< std::vector<uint> > LanguageOffsets(NumLanguages);
|
||||||
|
|
||||||
for (uint32 iLang = 0; iLang < NumLanguages; iLang++)
|
for (uint LanguageIdx = 0; LanguageIdx < NumLanguages; LanguageIdx++)
|
||||||
mpStringTable->mLangTables[iLang].Language = CFourCC(rSTRG);
|
|
||||||
|
|
||||||
for (uint32 iLang = 0; iLang < NumLanguages; iLang++)
|
|
||||||
{
|
{
|
||||||
LangOffsets[iLang].resize(NumStrings);
|
mpStringTable->mLanguages[LanguageIdx].Language = (ELanguage) STRG.ReadFourCC();
|
||||||
|
|
||||||
rSTRG.Seek(0x4, SEEK_CUR); // Skipping total string size
|
|
||||||
|
|
||||||
for (uint32 iStr = 0; iStr < NumStrings; iStr++)
|
|
||||||
LangOffsets[iLang][iStr] = rSTRG.ReadLong();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (uint LanguageIdx = 0; LanguageIdx < NumLanguages; LanguageIdx++)
|
||||||
|
{
|
||||||
|
LanguageOffsets[LanguageIdx].resize(NumStrings);
|
||||||
|
STRG.Skip(4); // Skipping total string size
|
||||||
|
|
||||||
|
for (uint StringIdx = 0; StringIdx < NumStrings; StringIdx++)
|
||||||
|
{
|
||||||
|
LanguageOffsets[LanguageIdx][StringIdx] = STRG.ReadLong();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some of the following code assumes that language 0 is English
|
||||||
|
ASSERT( mpStringTable->mLanguages[0].Language == ELanguage::English );
|
||||||
|
|
||||||
// Strings
|
// Strings
|
||||||
uint32 StringsStart = rSTRG.Tell();
|
uint StringsStart = STRG.Tell();
|
||||||
|
|
||||||
for (uint32 iLang = 0; iLang < NumLanguages; iLang++)
|
for (uint LanguageIdx = 0; LanguageIdx < NumLanguages; LanguageIdx++)
|
||||||
{
|
{
|
||||||
CStringTable::SLangTable *pLang = &mpStringTable->mLangTables[iLang];
|
CStringTable::SLanguageData& Language = mpStringTable->mLanguages[LanguageIdx];
|
||||||
pLang->Strings.resize(NumStrings);
|
Language.Strings.resize(NumStrings);
|
||||||
|
|
||||||
for (uint32 iStr = 0; iStr < NumStrings; iStr++)
|
for (uint StringIdx = 0; StringIdx < NumStrings; StringIdx++)
|
||||||
{
|
{
|
||||||
rSTRG.Seek(StringsStart + LangOffsets[iLang][iStr], SEEK_SET);
|
STRG.GoTo( StringsStart + LanguageOffsets[LanguageIdx][StringIdx] );
|
||||||
rSTRG.Seek(0x4, SEEK_CUR); // Skipping string size
|
STRG.Skip(4); // Skipping string size
|
||||||
|
Language.Strings[StringIdx].String = STRG.ReadString();
|
||||||
|
|
||||||
pLang->Strings[iStr] = rSTRG.ReadString();
|
// Flag the string as localized if it has a different offset than the English string
|
||||||
|
Language.Strings[StringIdx].IsLocalized = (LanguageIdx == 0 ||
|
||||||
|
LanguageOffsets[LanguageIdx][StringIdx] != LanguageOffsets[0][StringIdx]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CStringLoader::LoadNameTable(IInputStream& rSTRG)
|
void CStringLoader::LoadNameTable(IInputStream& STRG)
|
||||||
{
|
{
|
||||||
// Name table header
|
// Name table header
|
||||||
uint32 NameCount = rSTRG.ReadLong();
|
uint NameCount = STRG.ReadLong();
|
||||||
uint32 NameTableSize = rSTRG.ReadLong();
|
uint NameTableSize = STRG.ReadLong();
|
||||||
uint32 NameTableStart = rSTRG.Tell();
|
uint NameTableStart = STRG.Tell();
|
||||||
uint32 NameTableEnd = NameTableStart + NameTableSize;
|
uint NameTableEnd = NameTableStart + NameTableSize;
|
||||||
|
|
||||||
// Name definitions
|
// Name definitions
|
||||||
struct SNameDef {
|
struct SNameDef {
|
||||||
uint32 NameOffset, StringIndex;
|
uint NameOffset, StringIndex;
|
||||||
};
|
};
|
||||||
std::vector<SNameDef> NameDefs(NameCount);
|
std::vector<SNameDef> NameDefs(NameCount);
|
||||||
|
|
||||||
for (uint32 iName = 0; iName < NameCount; iName++)
|
// Keep track of max string index so we can size the names array appropriately.
|
||||||
|
// Note that it is possible that not every string in the table has a name.
|
||||||
|
int MaxIndex = -1;
|
||||||
|
|
||||||
|
for (uint NameIdx = 0; NameIdx < NameCount; NameIdx++)
|
||||||
{
|
{
|
||||||
NameDefs[iName].NameOffset = rSTRG.ReadLong() + NameTableStart;
|
NameDefs[NameIdx].NameOffset = STRG.ReadLong() + NameTableStart;
|
||||||
NameDefs[iName].StringIndex = rSTRG.ReadLong();
|
NameDefs[NameIdx].StringIndex = STRG.ReadLong();
|
||||||
|
MaxIndex = Math::Max(MaxIndex, (int) NameDefs[NameIdx].StringIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name strings
|
// Name strings
|
||||||
mpStringTable->mStringNames.resize(mpStringTable->mNumStrings);
|
mpStringTable->mStringNames.resize(MaxIndex + 1);
|
||||||
for (uint32 iName = 0; iName < NameCount; iName++)
|
|
||||||
|
for (uint NameIdx = 0; NameIdx < NameCount; NameIdx++)
|
||||||
{
|
{
|
||||||
SNameDef *pDef = &NameDefs[iName];
|
SNameDef& NameDef = NameDefs[NameIdx];
|
||||||
rSTRG.Seek(pDef->NameOffset, SEEK_SET);
|
STRG.GoTo(NameDef.NameOffset);
|
||||||
mpStringTable->mStringNames[pDef->StringIndex] = rSTRG.ReadString();
|
mpStringTable->mStringNames[NameDef.StringIndex] = STRG.ReadString();
|
||||||
}
|
}
|
||||||
rSTRG.Seek(NameTableEnd, SEEK_SET);
|
STRG.GoTo(NameTableEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ************ STATIC ************
|
// ************ STATIC ************
|
||||||
CStringTable* CStringLoader::LoadSTRG(IInputStream& rSTRG, CResourceEntry *pEntry)
|
CStringTable* CStringLoader::LoadSTRG(IInputStream& STRG, CResourceEntry* pEntry)
|
||||||
{
|
{
|
||||||
// Verify that this is a valid STRG
|
if (!STRG.IsValid()) return nullptr;
|
||||||
if (!rSTRG.IsValid()) return nullptr;
|
|
||||||
|
|
||||||
uint32 Magic = rSTRG.ReadLong();
|
// Verify that this is a valid STRG
|
||||||
|
uint Magic = STRG.ReadLong();
|
||||||
EGame Version = EGame::Invalid;
|
EGame Version = EGame::Invalid;
|
||||||
|
|
||||||
if (Magic != 0x87654321)
|
if (Magic != 0x87654321)
|
||||||
{
|
{
|
||||||
// Check for MP1 Demo STRG format - no magic/version; the first value is actually the filesize
|
// Check for MP1 Demo STRG format - no magic/version; the first value is actually the filesize
|
||||||
// so the best I can do is verify the first value actually points to the end of the file
|
// so the best I can do is verify the first value actually points to the end of the file.
|
||||||
if (Magic <= (uint32) rSTRG.Size())
|
// The file can have up to 31 padding bytes at the end so we account for that
|
||||||
|
if (Magic <= (uint) STRG.Size() && Magic > STRG.Size() - 32)
|
||||||
{
|
{
|
||||||
rSTRG.Seek(Magic, SEEK_SET);
|
|
||||||
if ((rSTRG.EoF()) || (rSTRG.ReadShort() == 0xFFFF))
|
|
||||||
Version = EGame::PrimeDemo;
|
Version = EGame::PrimeDemo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If not, then we seem to have an invalid file...
|
||||||
if (Version != EGame::PrimeDemo)
|
if (Version != EGame::PrimeDemo)
|
||||||
{
|
{
|
||||||
errorf("%s: Invalid STRG magic: 0x%08X", *rSTRG.GetSourceString(), Magic);
|
errorf("%s: Invalid STRG magic: 0x%08X", *STRG.GetSourceString(), Magic);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
uint32 FileVersion = rSTRG.ReadLong();
|
uint FileVersion = STRG.ReadLong();
|
||||||
Version = GetFormatVersion(FileVersion);
|
Version = GetFormatVersion(FileVersion);
|
||||||
|
|
||||||
if (Version == EGame::Invalid)
|
if (Version == EGame::Invalid)
|
||||||
{
|
{
|
||||||
errorf("%s: Unsupported STRG version: 0x%X", *rSTRG.GetSourceString(), FileVersion);
|
errorf("%s: Unrecognized STRG version: 0x%X", *STRG.GetSourceString(), FileVersion);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,9 +238,9 @@ CStringTable* CStringLoader::LoadSTRG(IInputStream& rSTRG, CResourceEntry *pEntr
|
||||||
Loader.mpStringTable = new CStringTable(pEntry);
|
Loader.mpStringTable = new CStringTable(pEntry);
|
||||||
Loader.mVersion = Version;
|
Loader.mVersion = Version;
|
||||||
|
|
||||||
if (Version == EGame::PrimeDemo) Loader.LoadPrimeDemoSTRG(rSTRG);
|
if (Version == EGame::PrimeDemo) Loader.LoadPrimeDemoSTRG(STRG);
|
||||||
else if (Version < EGame::Corruption) Loader.LoadPrimeSTRG(rSTRG);
|
else if (Version < EGame::Corruption) Loader.LoadPrimeSTRG(STRG);
|
||||||
else Loader.LoadCorruptionSTRG(rSTRG);
|
else Loader.LoadCorruptionSTRG(STRG);
|
||||||
|
|
||||||
return Loader.mpStringTable;
|
return Loader.mpStringTable;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
#define CSTRINGLOADER_H
|
#define CSTRINGLOADER_H
|
||||||
|
|
||||||
#include "Core/GameProject/CResourceStore.h"
|
#include "Core/GameProject/CResourceStore.h"
|
||||||
#include "Core/Resource/CStringTable.h"
|
|
||||||
#include "Core/Resource/TResPtr.h"
|
#include "Core/Resource/TResPtr.h"
|
||||||
|
#include "Core/Resource/StringTable/CStringTable.h"
|
||||||
#include <Common/EGame.h>
|
#include <Common/EGame.h>
|
||||||
|
|
||||||
class CStringLoader
|
class CStringLoader
|
||||||
|
@ -12,14 +12,14 @@ class CStringLoader
|
||||||
EGame mVersion;
|
EGame mVersion;
|
||||||
|
|
||||||
CStringLoader() {}
|
CStringLoader() {}
|
||||||
void LoadPrimeDemoSTRG(IInputStream& rSTRG);
|
void LoadPrimeDemoSTRG(IInputStream& STRG);
|
||||||
void LoadPrimeSTRG(IInputStream& rSTRG);
|
void LoadPrimeSTRG(IInputStream& STRG);
|
||||||
void LoadCorruptionSTRG(IInputStream& rSTRG);
|
void LoadCorruptionSTRG(IInputStream& STRG);
|
||||||
void LoadNameTable(IInputStream& rSTRG);
|
void LoadNameTable(IInputStream& STRG);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static CStringTable* LoadSTRG(IInputStream &rSTRG, CResourceEntry *pEntry);
|
static CStringTable* LoadSTRG(IInputStream& STRG, CResourceEntry* pEntry);
|
||||||
static EGame GetFormatVersion(uint32 Version);
|
static EGame GetFormatVersion(uint Version);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CSTRINGLOADER_H
|
#endif // CSTRINGLOADER_H
|
||||||
|
|
|
@ -562,7 +562,7 @@ void CTextureDecoder::ReadPixelC8(IInputStream& rSrc, IOutputStream& rDst)
|
||||||
((Index >> 2) & 0x1) ? G = 0xFF : G = 0x0;
|
((Index >> 2) & 0x1) ? G = 0xFF : G = 0x0;
|
||||||
((Index >> 1) & 0x1) ? B = 0xFF : B = 0x0;
|
((Index >> 1) & 0x1) ? B = 0xFF : B = 0x0;
|
||||||
((Index >> 0) & 0x1) ? A = 0xFF : A = 0x0;
|
((Index >> 0) & 0x1) ? A = 0xFF : A = 0x0;
|
||||||
u32 RGBA = (R << 24) | (G << 16) | (B << 8) | (A);
|
uint32 RGBA = (R << 24) | (G << 16) | (B << 8) | (A);
|
||||||
dst.WriteLong(RGBA);*/
|
dst.WriteLong(RGBA);*/
|
||||||
|
|
||||||
mPaletteInput.Seek(Index * 2, SEEK_SET);
|
mPaletteInput.Seek(Index * 2, SEEK_SET);
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
#include "CFont.h"
|
#include "CFont.h"
|
||||||
#include "CPoiToWorld.h"
|
#include "CPoiToWorld.h"
|
||||||
#include "CResource.h"
|
#include "CResource.h"
|
||||||
#include "CScan.h"
|
|
||||||
#include "CStringTable.h"
|
|
||||||
#include "CTexture.h"
|
#include "CTexture.h"
|
||||||
#include "CWorld.h"
|
#include "CWorld.h"
|
||||||
#include "Core/Resource/Animation/CAnimation.h"
|
#include "Core/Resource/Animation/CAnimation.h"
|
||||||
|
@ -16,6 +14,8 @@
|
||||||
#include "Core/Resource/Animation/CSkin.h"
|
#include "Core/Resource/Animation/CSkin.h"
|
||||||
#include "Core/Resource/Area/CGameArea.h"
|
#include "Core/Resource/Area/CGameArea.h"
|
||||||
#include "Core/Resource/Model/CModel.h"
|
#include "Core/Resource/Model/CModel.h"
|
||||||
|
#include "Core/Resource/Scan/CScan.h"
|
||||||
|
#include "Core/Resource/StringTable/CStringTable.h"
|
||||||
|
|
||||||
#endif // RESOURCES_H
|
#endif // RESOURCES_H
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -0,0 +1,15 @@
|
||||||
|
#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,
|
||||||
|
Artifacts = 5
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ELOGBOOKCATEGORY_H
|
|
@ -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
|
|
@ -13,6 +13,7 @@ void CGameTemplate::Serialize(IArchive& Arc)
|
||||||
{
|
{
|
||||||
Arc << SerialParameter("ScriptObjects", mScriptTemplates)
|
Arc << SerialParameter("ScriptObjects", mScriptTemplates)
|
||||||
<< SerialParameter("PropertyArchetypes", mPropertyTemplates)
|
<< SerialParameter("PropertyArchetypes", mPropertyTemplates)
|
||||||
|
<< SerialParameter("MiscTemplates", mMiscTemplates)
|
||||||
<< SerialParameter("States", mStates)
|
<< SerialParameter("States", mStates)
|
||||||
<< SerialParameter("Messages", mMessages);
|
<< SerialParameter("Messages", mMessages);
|
||||||
}
|
}
|
||||||
|
@ -51,6 +52,13 @@ void CGameTemplate::Load(const TString& kFilePath)
|
||||||
Internal_LoadPropertyTemplate(Iter->second);
|
Internal_LoadPropertyTemplate(Iter->second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto Iter = mMiscTemplates.begin(); Iter != mMiscTemplates.end(); Iter++)
|
||||||
|
{
|
||||||
|
SScriptTemplatePath& MiscPath = Iter->second;
|
||||||
|
TString AbsPath = gkGameRoot + MiscPath.Path;
|
||||||
|
MiscPath.pTemplate = std::make_shared<CScriptTemplate>(this, -1, AbsPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CGameTemplate::Save()
|
void CGameTemplate::Save()
|
||||||
|
@ -118,6 +126,16 @@ void CGameTemplate::SaveGameTemplates(bool ForceAll /*= false*/)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto Iter = mMiscTemplates.begin(); Iter != mMiscTemplates.end(); Iter++)
|
||||||
|
{
|
||||||
|
SScriptTemplatePath& Path = Iter->second;
|
||||||
|
|
||||||
|
if( Path.pTemplate )
|
||||||
|
{
|
||||||
|
Path.pTemplate->Save(ForceAll);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 CGameTemplate::GameVersion(TString VersionName)
|
uint32 CGameTemplate::GameVersion(TString VersionName)
|
||||||
|
@ -286,6 +304,21 @@ bool CGameTemplate::RenamePropertyArchetype(const TString& kTypeName, const TStr
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CScriptTemplate* CGameTemplate::FindMiscTemplate(const TString& kTemplateName)
|
||||||
|
{
|
||||||
|
auto Iter = mMiscTemplates.find(kTemplateName);
|
||||||
|
|
||||||
|
if (Iter == mMiscTemplates.end())
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SScriptTemplatePath& Path = Iter->second;
|
||||||
|
return Path.pTemplate.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TString CGameTemplate::GetGameDirectory() const
|
TString CGameTemplate::GetGameDirectory() const
|
||||||
{
|
{
|
||||||
return mSourceFile.GetFileDirectory();
|
return mSourceFile.GetFileDirectory();
|
||||||
|
|
|
@ -35,54 +35,23 @@ struct SObjId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Struct holding a reference to a script object template */
|
/** Struct holding a reference to a template */
|
||||||
struct SScriptTemplatePath
|
template<typename TemplateT>
|
||||||
|
struct TTemplatePath
|
||||||
{
|
{
|
||||||
/** File path to the template file, relative to the game directory */
|
/** File path to the template file, relative to the game directory */
|
||||||
TString Path;
|
TString Path;
|
||||||
|
|
||||||
/** Template in memory */
|
/** Template in memory */
|
||||||
std::shared_ptr<CScriptTemplate> pTemplate;
|
std::shared_ptr<TemplateT> pTemplate;
|
||||||
|
|
||||||
/** Constructor */
|
/** Constructor */
|
||||||
SScriptTemplatePath()
|
TTemplatePath()
|
||||||
{}
|
{}
|
||||||
|
|
||||||
SScriptTemplatePath(const TString& kInPath, CScriptTemplate* pInTemplate)
|
TTemplatePath(const TString& kInPath, TemplateT* pInTemplate)
|
||||||
: Path(kInPath)
|
: Path(kInPath)
|
||||||
, pTemplate( std::shared_ptr<CScriptTemplate>(pInTemplate) )
|
, pTemplate( std::shared_ptr<TemplateT>(pInTemplate) )
|
||||||
{}
|
|
||||||
|
|
||||||
/** Serializer */
|
|
||||||
void Serialize(IArchive& Arc)
|
|
||||||
{
|
|
||||||
if (Arc.FileVersion() == 0)
|
|
||||||
{
|
|
||||||
Arc << SerialParameter("Path", Path, SH_Attribute);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Arc.SerializePrimitive(Path, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Struct holding a reference to a property template */
|
|
||||||
struct SPropertyTemplatePath
|
|
||||||
{
|
|
||||||
/** File path to the template file, relative to the game directory */
|
|
||||||
TString Path;
|
|
||||||
|
|
||||||
/** Template in memory */
|
|
||||||
std::shared_ptr<IProperty> pTemplate;
|
|
||||||
|
|
||||||
/** Constructor */
|
|
||||||
SPropertyTemplatePath()
|
|
||||||
{}
|
|
||||||
|
|
||||||
SPropertyTemplatePath(const TString& kInPath, IProperty* pInTemplate)
|
|
||||||
: Path(kInPath)
|
|
||||||
, pTemplate( std::shared_ptr<IProperty>(pInTemplate) )
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/** Serializer */
|
/** Serializer */
|
||||||
|
@ -92,6 +61,9 @@ struct SPropertyTemplatePath
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef TTemplatePath<CScriptTemplate> SScriptTemplatePath;
|
||||||
|
typedef TTemplatePath<IProperty> SPropertyTemplatePath;
|
||||||
|
|
||||||
/** CGameTemplate - Per-game template data */
|
/** CGameTemplate - Per-game template data */
|
||||||
class CGameTemplate
|
class CGameTemplate
|
||||||
{
|
{
|
||||||
|
@ -103,6 +75,7 @@ class CGameTemplate
|
||||||
/** Template arrays */
|
/** Template arrays */
|
||||||
std::map<SObjId, SScriptTemplatePath> mScriptTemplates;
|
std::map<SObjId, SScriptTemplatePath> mScriptTemplates;
|
||||||
std::map<TString, SPropertyTemplatePath> mPropertyTemplates;
|
std::map<TString, SPropertyTemplatePath> mPropertyTemplates;
|
||||||
|
std::map<TString, SScriptTemplatePath> mMiscTemplates;
|
||||||
|
|
||||||
std::map<SObjId, TString> mStates;
|
std::map<SObjId, TString> mStates;
|
||||||
std::map<SObjId, TString> mMessages;
|
std::map<SObjId, TString> mMessages;
|
||||||
|
@ -130,6 +103,7 @@ public:
|
||||||
IProperty* FindPropertyArchetype(const TString& kTypeName);
|
IProperty* FindPropertyArchetype(const TString& kTypeName);
|
||||||
TString GetPropertyArchetypeFilePath(const TString& kTypeName);
|
TString GetPropertyArchetypeFilePath(const TString& kTypeName);
|
||||||
bool RenamePropertyArchetype(const TString& kTypeName, const TString& kNewTypeName);
|
bool RenamePropertyArchetype(const TString& kTypeName, const TString& kNewTypeName);
|
||||||
|
CScriptTemplate* FindMiscTemplate(const TString& kTemplateName);
|
||||||
TString GetGameDirectory() const;
|
TString GetGameDirectory() const;
|
||||||
|
|
||||||
// Inline Accessors
|
// Inline Accessors
|
||||||
|
|
|
@ -106,7 +106,8 @@ struct SNameValue
|
||||||
bool IsValid;
|
bool IsValid;
|
||||||
|
|
||||||
/** List of all properties using this ID */
|
/** List of all properties using this ID */
|
||||||
std::list<IProperty*> PropertyList;
|
/** @todo - make this an intrusively linked list */
|
||||||
|
std::set<IProperty*> PropertyList;
|
||||||
|
|
||||||
void Serialize(IArchive& Arc)
|
void Serialize(IArchive& Arc)
|
||||||
{
|
{
|
||||||
|
@ -285,7 +286,7 @@ bool IsValidPropertyID(uint32 ID, const char* pkTypeName, bool* pOutIsValid /*=
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Retrieves a list of all properties that match the requested property ID. */
|
/** Retrieves a list of all properties that match the requested property ID. */
|
||||||
void RetrievePropertiesWithID(uint32 ID, const char* pkTypeName, std::list<IProperty*>& OutList)
|
void RetrievePropertiesWithID(uint32 ID, const char* pkTypeName, std::vector<IProperty*>& OutList)
|
||||||
{
|
{
|
||||||
SNameKey Key = CreateKey(ID, pkTypeName);
|
SNameKey Key = CreateKey(ID, pkTypeName);
|
||||||
auto MapFind = gNameMap.find(Key);
|
auto MapFind = gNameMap.find(Key);
|
||||||
|
@ -293,7 +294,12 @@ void RetrievePropertiesWithID(uint32 ID, const char* pkTypeName, std::list<IProp
|
||||||
if (MapFind != gNameMap.end())
|
if (MapFind != gNameMap.end())
|
||||||
{
|
{
|
||||||
SNameValue& Value = MapFind->second;
|
SNameValue& Value = MapFind->second;
|
||||||
OutList = Value.PropertyList;
|
OutList.reserve(Value.PropertyList.size());
|
||||||
|
|
||||||
|
for (auto Iter = Value.PropertyList.begin(); Iter != Value.PropertyList.end(); Iter++)
|
||||||
|
{
|
||||||
|
OutList.push_back(*Iter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,7 +397,7 @@ void ChangeTypeName(IProperty* pProperty, const char* pkOldTypeName, const char*
|
||||||
if (Find != gNameMap.end())
|
if (Find != gNameMap.end())
|
||||||
{
|
{
|
||||||
SNameValue& Value = Find->second;
|
SNameValue& Value = Find->second;
|
||||||
WasRegistered = NBasics::ListRemoveOne(Value.PropertyList, pProperty);
|
WasRegistered = (Value.PropertyList.find(pProperty) != Value.PropertyList.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a key for the new property and add it to the list.
|
// Create a key for the new property and add it to the list.
|
||||||
|
@ -409,7 +415,7 @@ void ChangeTypeName(IProperty* pProperty, const char* pkOldTypeName, const char*
|
||||||
|
|
||||||
if (WasRegistered)
|
if (WasRegistered)
|
||||||
{
|
{
|
||||||
Find->second.PropertyList.push_back(pProperty);
|
Find->second.PropertyList.insert(pProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
gMapIsDirty = true;
|
gMapIsDirty = true;
|
||||||
|
@ -527,7 +533,7 @@ void RegisterProperty(IProperty* pProperty)
|
||||||
pProperty->SetName( MapFind->second.Name );
|
pProperty->SetName( MapFind->second.Name );
|
||||||
}
|
}
|
||||||
|
|
||||||
MapFind->second.PropertyList.push_back(pProperty);
|
MapFind->second.PropertyList.insert(pProperty);
|
||||||
|
|
||||||
// Update the property's Name field to match the mapped name.
|
// Update the property's Name field to match the mapped name.
|
||||||
pProperty->SetName( MapFind->second.Name );
|
pProperty->SetName( MapFind->second.Name );
|
||||||
|
@ -543,7 +549,7 @@ void UnregisterProperty(IProperty* pProperty)
|
||||||
{
|
{
|
||||||
// Found the value, now remove the element from the list.
|
// Found the value, now remove the element from the list.
|
||||||
SNameValue& Value = Iter->second;
|
SNameValue& Value = Iter->second;
|
||||||
NBasics::ListRemoveOne(Value.PropertyList, pProperty);
|
Value.PropertyList.erase(pProperty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ uint32 CalculatePropertyID(const char* pkName, const char* pkTypeName);
|
||||||
bool IsValidPropertyID(uint32 ID, const char* pkTypeName, bool* pOutIsValid = nullptr);
|
bool IsValidPropertyID(uint32 ID, const char* pkTypeName, bool* pOutIsValid = nullptr);
|
||||||
|
|
||||||
/** Retrieves a list of all properties that match the requested property ID. */
|
/** Retrieves a list of all properties that match the requested property ID. */
|
||||||
void RetrievePropertiesWithID(uint32 ID, const char* pkTypeName, std::list<IProperty*>& OutList);
|
void RetrievePropertiesWithID(uint32 ID, const char* pkTypeName, std::vector<IProperty*>& OutList);
|
||||||
|
|
||||||
/** Retrieves a list of all XML templates that contain a given property ID. */
|
/** Retrieves a list of all XML templates that contain a given property ID. */
|
||||||
void RetrieveXMLsWithProperty(uint32 ID, const char* pkTypeName, std::set<TString>& OutSet);
|
void RetrieveXMLsWithProperty(uint32 ID, const char* pkTypeName, std::set<TString>& OutSet);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#define CANIMATIONSETPROPERTY_H
|
#define CANIMATIONSETPROPERTY_H
|
||||||
|
|
||||||
#include "IProperty.h"
|
#include "IProperty.h"
|
||||||
|
#include "Core/Resource/Animation/CAnimationParameters.h"
|
||||||
|
|
||||||
class CAnimationSetProperty : public TSerializeableTypedProperty< CAnimationParameters, EPropertyType::AnimationSet >
|
class CAnimationSetProperty : public TSerializeableTypedProperty< CAnimationParameters, EPropertyType::AnimationSet >
|
||||||
{
|
{
|
||||||
|
@ -17,7 +18,7 @@ protected:
|
||||||
public:
|
public:
|
||||||
virtual void SerializeValue(void* pData, IArchive& Arc) const
|
virtual void SerializeValue(void* pData, IArchive& Arc) const
|
||||||
{
|
{
|
||||||
Value(pData).Serialize(Arc);
|
ValueRef(pData).Serialize(Arc);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual const char* HashableTypeName() const
|
virtual const char* HashableTypeName() const
|
||||||
|
|
|
@ -25,7 +25,7 @@ public:
|
||||||
|
|
||||||
virtual void SerializeValue(void* pData, IArchive& Arc) const
|
virtual void SerializeValue(void* pData, IArchive& Arc) const
|
||||||
{
|
{
|
||||||
Value(pData).Serialize(Arc);
|
ValueRef(pData).Serialize(Arc);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -69,9 +69,9 @@ public:
|
||||||
// Skip TSerializeableTypedProperty, serialize default value ourselves so we can set SH_HexDisplay
|
// Skip TSerializeableTypedProperty, serialize default value ourselves so we can set SH_HexDisplay
|
||||||
TTypedProperty::Serialize(rArc);
|
TTypedProperty::Serialize(rArc);
|
||||||
|
|
||||||
|
// Serialize default value
|
||||||
TEnumPropertyBase* pArchetype = static_cast<TEnumPropertyBase*>(mpArchetype);
|
TEnumPropertyBase* pArchetype = static_cast<TEnumPropertyBase*>(mpArchetype);
|
||||||
uint32 DefaultValueFlags = SH_HexDisplay | (pArchetype || Game() <= EGame::Prime ? SH_Optional : 0);
|
uint32 DefaultValueFlags = SH_Optional | (TypeEnum == EPropertyType::Enum ? SH_HexDisplay : 0);
|
||||||
|
|
||||||
rArc << SerialParameter("DefaultValue", mDefaultValue, DefaultValueFlags, pArchetype ? pArchetype->mDefaultValue : 0);
|
rArc << SerialParameter("DefaultValue", mDefaultValue, DefaultValueFlags, pArchetype ? pArchetype->mDefaultValue : 0);
|
||||||
|
|
||||||
// Only serialize type name override for root archetypes.
|
// Only serialize type name override for root archetypes.
|
||||||
|
|
|
@ -69,7 +69,7 @@ void CPropertyNameGenerator::Generate(const SPropertyNameGenerationParameters& r
|
||||||
}
|
}
|
||||||
|
|
||||||
// If TestIntsAsChoices is enabled, and int is in the type list, then choice must be in the type list too.
|
// If TestIntsAsChoices is enabled, and int is in the type list, then choice must be in the type list too.
|
||||||
if (rkParams.TestIntsAsChoices && NBasics::VectorContains(mTypeNames, TString("int")))
|
if (rkParams.TestIntsAsChoices && NBasics::VectorFind(mTypeNames, TString("int")) >= 0)
|
||||||
{
|
{
|
||||||
NBasics::VectorAddUnique(mTypeNames, TString("choice"));
|
NBasics::VectorAddUnique(mTypeNames, TString("choice"));
|
||||||
}
|
}
|
||||||
|
@ -218,7 +218,7 @@ void CPropertyNameGenerator::Generate(const SPropertyNameGenerationParameters& r
|
||||||
// If we have too many saved results, then to avoid crashing we will force enable log output.
|
// If we have too many saved results, then to avoid crashing we will force enable log output.
|
||||||
if (mGeneratedNames.size() > 9999)
|
if (mGeneratedNames.size() > 9999)
|
||||||
{
|
{
|
||||||
gpUIRelay->AsyncMessageBox("Warning", "There are over 10,000 results. Results will no longer print to the screen. Check the log for the remaining output.");
|
gpUIRelay->ShowMessageBoxAsync("Warning", "There are over 10,000 results. Results will no longer print to the screen. Check the log for the remaining output.");
|
||||||
WriteToLog = true;
|
WriteToLog = true;
|
||||||
SaveResults = false;
|
SaveResults = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,14 @@ void CStructProperty::RevertToDefault(void* pData) const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CStructProperty::SetDefaultFromData(void* pData)
|
||||||
|
{
|
||||||
|
for (int ChildIdx = 0; ChildIdx < mChildren.size(); ChildIdx++)
|
||||||
|
{
|
||||||
|
mChildren[ChildIdx]->SetDefaultFromData(pData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const char* CStructProperty::HashableTypeName() const
|
const char* CStructProperty::HashableTypeName() const
|
||||||
{
|
{
|
||||||
return mpArchetype ? mpArchetype->HashableTypeName() : *mName;
|
return mpArchetype ? mpArchetype->HashableTypeName() : *mName;
|
||||||
|
|
|
@ -25,6 +25,7 @@ public:
|
||||||
virtual void Destruct(void* pData) const;
|
virtual void Destruct(void* pData) const;
|
||||||
virtual bool MatchesDefault(void* pData) const;
|
virtual bool MatchesDefault(void* pData) const;
|
||||||
virtual void RevertToDefault(void* pData) const;
|
virtual void RevertToDefault(void* pData) const;
|
||||||
|
virtual void SetDefaultFromData(void* pData);
|
||||||
virtual const char* HashableTypeName() const;
|
virtual const char* HashableTypeName() const;
|
||||||
virtual void Serialize(IArchive& rArc);
|
virtual void Serialize(IArchive& rArc);
|
||||||
virtual void SerializeValue(void* pData, IArchive& Arc) const;
|
virtual void SerializeValue(void* pData, IArchive& Arc) const;
|
||||||
|
|
|
@ -91,6 +91,7 @@ void IProperty::Serialize(IArchive& rArc)
|
||||||
|
|
||||||
// The archetype must exist, or else the template file is malformed.
|
// The archetype must exist, or else the template file is malformed.
|
||||||
ASSERT(pArchetype != nullptr);
|
ASSERT(pArchetype != nullptr);
|
||||||
|
ASSERT(pArchetype->Type() == Type());
|
||||||
|
|
||||||
InitFromArchetype(pArchetype);
|
InitFromArchetype(pArchetype);
|
||||||
}
|
}
|
||||||
|
@ -325,7 +326,29 @@ TString IProperty::GetTemplateFileName()
|
||||||
|
|
||||||
bool IProperty::ShouldCook(void* pPropertyData) const
|
bool IProperty::ShouldCook(void* pPropertyData) const
|
||||||
{
|
{
|
||||||
switch (mCookPreference)
|
ECookPreference Preference = mCookPreference;
|
||||||
|
|
||||||
|
// Determine the real cook preference to use.
|
||||||
|
if (Preference == ECookPreference::Default)
|
||||||
|
{
|
||||||
|
if (Game() == EGame::DKCReturns)
|
||||||
|
{
|
||||||
|
// DKCR properties usually don't write unless they have been modified.
|
||||||
|
Preference = ECookPreference::OnlyIfModified;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// MP2 and MP3 properties usually always write no matter what.
|
||||||
|
Preference = ECookPreference::Always;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Preference == ECookPreference::OnlyIfModified && Game() <= EGame::Prime)
|
||||||
|
{
|
||||||
|
// OnlyIfModified not supported for MP1.
|
||||||
|
Preference = ECookPreference::Always;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (Preference)
|
||||||
{
|
{
|
||||||
case ECookPreference::Always:
|
case ECookPreference::Always:
|
||||||
return true;
|
return true;
|
||||||
|
@ -333,8 +356,13 @@ bool IProperty::ShouldCook(void* pPropertyData) const
|
||||||
case ECookPreference::Never:
|
case ECookPreference::Never:
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
case ECookPreference::OnlyIfModified:
|
||||||
|
return !MatchesDefault(pPropertyData);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return (Game() < EGame::DKCReturns ? true : !MatchesDefault(pPropertyData));
|
// Unhandled case
|
||||||
|
ASSERT(false);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#ifndef IPROPERTY_H
|
#ifndef IPROPERTY_H
|
||||||
#define IPROPERTY_H
|
#define IPROPERTY_H
|
||||||
|
|
||||||
#include "Core/Resource/Animation/CAnimationParameters.h"
|
|
||||||
#include <Common/Common.h>
|
#include <Common/Common.h>
|
||||||
#include <Common/CFourCC.h>
|
#include <Common/CFourCC.h>
|
||||||
#include <Common/Math/CVector3f.h>
|
#include <Common/Math/CVector3f.h>
|
||||||
|
@ -106,7 +105,8 @@ enum class ECookPreference
|
||||||
{
|
{
|
||||||
Default,
|
Default,
|
||||||
Always,
|
Always,
|
||||||
Never
|
Never,
|
||||||
|
OnlyIfModified
|
||||||
};
|
};
|
||||||
|
|
||||||
/** New property class */
|
/** New property class */
|
||||||
|
@ -178,6 +178,7 @@ public:
|
||||||
virtual void PostInitialize() {}
|
virtual void PostInitialize() {}
|
||||||
virtual void PropertyValueChanged(void* pPropertyData) {}
|
virtual void PropertyValueChanged(void* pPropertyData) {}
|
||||||
virtual void CopyDefaultValueTo(IProperty* pOtherProperty) {}
|
virtual void CopyDefaultValueTo(IProperty* pOtherProperty) {}
|
||||||
|
virtual void SetDefaultFromData(void* pData) {}
|
||||||
virtual bool IsNumericalType() const { return false; }
|
virtual bool IsNumericalType() const { return false; }
|
||||||
virtual bool IsPointerType() const { return false; }
|
virtual bool IsPointerType() const { return false; }
|
||||||
virtual TString ValueAsString(void* pData) const { return ""; }
|
virtual TString ValueAsString(void* pData) const { return ""; }
|
||||||
|
@ -367,6 +368,7 @@ public:
|
||||||
virtual void Destruct(void* pData) const { ValueRef(pData).~PropType(); }
|
virtual void Destruct(void* pData) const { ValueRef(pData).~PropType(); }
|
||||||
virtual bool MatchesDefault(void* pData) const { return ValueRef(pData) == mDefaultValue; }
|
virtual bool MatchesDefault(void* pData) const { return ValueRef(pData) == mDefaultValue; }
|
||||||
virtual void RevertToDefault(void* pData) const { ValueRef(pData) = mDefaultValue; }
|
virtual void RevertToDefault(void* pData) const { ValueRef(pData) = mDefaultValue; }
|
||||||
|
virtual void SetDefaultFromData(void* pData) { mDefaultValue = ValueRef(pData); MarkDirty(); }
|
||||||
|
|
||||||
virtual bool CanHaveDefault() const { return true; }
|
virtual bool CanHaveDefault() const { return true; }
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,396 @@
|
||||||
|
#include "CStringTable.h"
|
||||||
|
#include "Core/GameProject/CGameProject.h"
|
||||||
|
#include <Common/Math/MathUtil.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listing of supported languages for different engine versions. Note we ignore the "unused" languages.
|
||||||
|
* This is also the order that languages appear in game STRG assets.
|
||||||
|
*/
|
||||||
|
// Supported languages in the original NTSC release of Metroid Prime
|
||||||
|
const std::vector<ELanguage> gkSupportedLanguagesMP1 =
|
||||||
|
{
|
||||||
|
ELanguage::English
|
||||||
|
};
|
||||||
|
|
||||||
|
// Supported languages in the PAL version of Metroid Prime, and also Metroid Prime 2
|
||||||
|
const std::vector<ELanguage> gkSupportedLanguagesMP1PAL =
|
||||||
|
{
|
||||||
|
ELanguage::English, ELanguage::French, ELanguage::German,
|
||||||
|
ELanguage::Spanish, ELanguage::Italian, ELanguage::Japanese
|
||||||
|
};
|
||||||
|
|
||||||
|
// Supported languages in Metroid Prime 3
|
||||||
|
const std::vector<ELanguage> gkSupportedLanguagesMP3 =
|
||||||
|
{
|
||||||
|
ELanguage::English, ELanguage::Japanese, ELanguage::German,
|
||||||
|
ELanguage::French, ELanguage::Spanish, ELanguage::Italian
|
||||||
|
};
|
||||||
|
|
||||||
|
// Supported languages in DKCR
|
||||||
|
const std::vector<ELanguage> gkSupportedLanguagesDKCR =
|
||||||
|
{
|
||||||
|
ELanguage::English, ELanguage::Japanese, ELanguage::German,
|
||||||
|
ELanguage::French, ELanguage::Spanish, ELanguage::Italian,
|
||||||
|
ELanguage::UKEnglish, ELanguage::Korean,
|
||||||
|
ELanguage::NAFrench, ELanguage::NASpanish
|
||||||
|
};
|
||||||
|
|
||||||
|
// Utility function - retrieve the language array for a given game/region
|
||||||
|
static const std::vector<ELanguage>& GetSupportedLanguages(EGame Game, ERegion Region)
|
||||||
|
{
|
||||||
|
switch (Game)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case EGame::PrimeDemo:
|
||||||
|
case EGame::Prime:
|
||||||
|
if (Region == ERegion::NTSC)
|
||||||
|
return gkSupportedLanguagesMP1;
|
||||||
|
else
|
||||||
|
return gkSupportedLanguagesMP1PAL;
|
||||||
|
|
||||||
|
case EGame::EchoesDemo:
|
||||||
|
case EGame::Echoes:
|
||||||
|
case EGame::CorruptionProto:
|
||||||
|
return gkSupportedLanguagesMP1PAL;
|
||||||
|
|
||||||
|
case EGame::Corruption:
|
||||||
|
return gkSupportedLanguagesMP3;
|
||||||
|
|
||||||
|
case EGame::DKCReturns:
|
||||||
|
return gkSupportedLanguagesDKCR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility function - retrieve the index of a given language
|
||||||
|
static int FindLanguageIndex(const CStringTable* pkInTable, ELanguage InLanguage)
|
||||||
|
{
|
||||||
|
for (uint LanguageIdx = 0; LanguageIdx < pkInTable->NumLanguages(); LanguageIdx++)
|
||||||
|
{
|
||||||
|
if (pkInTable->LanguageByIndex(LanguageIdx) == InLanguage)
|
||||||
|
{
|
||||||
|
return LanguageIdx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a string given a language/index pair */
|
||||||
|
TString CStringTable::GetString(ELanguage Language, uint StringIndex) const
|
||||||
|
{
|
||||||
|
int LanguageIdx = FindLanguageIndex(this, Language);
|
||||||
|
|
||||||
|
if (LanguageIdx >= 0 && mLanguages[LanguageIdx].Strings.size() > StringIndex)
|
||||||
|
{
|
||||||
|
return mLanguages[LanguageIdx].Strings[StringIndex].String;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates a string for a given language */
|
||||||
|
void CStringTable::SetString(ELanguage Language, uint StringIndex, const TString& kNewString)
|
||||||
|
{
|
||||||
|
int LanguageIdx = FindLanguageIndex(this, Language);
|
||||||
|
|
||||||
|
if (LanguageIdx >= 0 && mLanguages[LanguageIdx].Strings.size() > StringIndex)
|
||||||
|
{
|
||||||
|
mLanguages[LanguageIdx].Strings[StringIndex].String = kNewString;
|
||||||
|
mLanguages[LanguageIdx].Strings[StringIndex].IsLocalized =
|
||||||
|
(LanguageIdx == 0 || kNewString != mLanguages[0].Strings[StringIndex].String);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates a string name */
|
||||||
|
void CStringTable::SetStringName(uint StringIndex, const TString& kNewName)
|
||||||
|
{
|
||||||
|
// Sanity check - make sure the string index is valid
|
||||||
|
ASSERT( NumStrings() > StringIndex );
|
||||||
|
|
||||||
|
// Expand the name listing if needed and assign the name
|
||||||
|
if (mStringNames.size() <= StringIndex)
|
||||||
|
{
|
||||||
|
mStringNames.resize( StringIndex + 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
mStringNames[StringIndex] = kNewName;
|
||||||
|
|
||||||
|
// Strip empty string names
|
||||||
|
while (mStringNames.back().IsEmpty())
|
||||||
|
mStringNames.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Move string to another position in the table */
|
||||||
|
void CStringTable::MoveString(uint StringIndex, uint NewIndex)
|
||||||
|
{
|
||||||
|
ASSERT( NumStrings() > StringIndex );
|
||||||
|
ASSERT( NumStrings() > NewIndex );
|
||||||
|
|
||||||
|
if (NewIndex == StringIndex)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Update string data
|
||||||
|
for (uint LanguageIdx = 0; LanguageIdx < mLanguages.size(); LanguageIdx++)
|
||||||
|
{
|
||||||
|
SLanguageData& Language = mLanguages[LanguageIdx];
|
||||||
|
SStringData String = Language.Strings[StringIndex];
|
||||||
|
|
||||||
|
if (NewIndex > StringIndex)
|
||||||
|
{
|
||||||
|
for (uint i=StringIndex; i<NewIndex; i++)
|
||||||
|
Language.Strings[i] = Language.Strings[i+1];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (uint i=StringIndex; i>NewIndex; i--)
|
||||||
|
Language.Strings[i] = Language.Strings[i-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
Language.Strings[NewIndex] = String;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update string name
|
||||||
|
uint MinIndex = Math::Min(StringIndex, NewIndex);
|
||||||
|
uint MaxIndex = Math::Max(StringIndex, NewIndex);
|
||||||
|
|
||||||
|
if (MinIndex < mStringNames.size())
|
||||||
|
{
|
||||||
|
if (MaxIndex >= mStringNames.size())
|
||||||
|
{
|
||||||
|
mStringNames.resize(MaxIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TString Name = mStringNames[StringIndex];
|
||||||
|
|
||||||
|
if (NewIndex > StringIndex)
|
||||||
|
{
|
||||||
|
for (uint i=StringIndex; i<NewIndex; i++)
|
||||||
|
mStringNames[i] = mStringNames[i+1];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (uint i=StringIndex; i>NewIndex; i--)
|
||||||
|
mStringNames[i] = mStringNames[i-1];
|
||||||
|
}
|
||||||
|
mStringNames[NewIndex] = Name;
|
||||||
|
|
||||||
|
// Strip empty string names
|
||||||
|
while (mStringNames.back().IsEmpty())
|
||||||
|
mStringNames.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add a new string to the table */
|
||||||
|
void CStringTable::AddString(uint AtIndex)
|
||||||
|
{
|
||||||
|
if (AtIndex < NumStrings())
|
||||||
|
{
|
||||||
|
if (mStringNames.size() > AtIndex)
|
||||||
|
{
|
||||||
|
mStringNames.insert( mStringNames.begin() + AtIndex, 1, "" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
AtIndex = NumStrings();
|
||||||
|
|
||||||
|
for (uint LanguageIdx = 0; LanguageIdx < mLanguages.size(); LanguageIdx++)
|
||||||
|
{
|
||||||
|
SLanguageData& Language = mLanguages[LanguageIdx];
|
||||||
|
Language.Strings.insert( Language.Strings.begin() + AtIndex, 1, SStringData() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Remove a string from the table */
|
||||||
|
void CStringTable::RemoveString(uint StringIndex)
|
||||||
|
{
|
||||||
|
ASSERT( StringIndex < NumStrings() );
|
||||||
|
|
||||||
|
if (mStringNames.size() > StringIndex)
|
||||||
|
mStringNames.erase( mStringNames.begin() + StringIndex );
|
||||||
|
|
||||||
|
for (uint LanguageIdx = 0; LanguageIdx < mLanguages.size(); LanguageIdx++)
|
||||||
|
{
|
||||||
|
SLanguageData& Language = mLanguages[LanguageIdx];
|
||||||
|
Language.Strings.erase( Language.Strings.begin() + StringIndex );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Initialize new resource data */
|
||||||
|
void CStringTable::InitializeNewResource()
|
||||||
|
{
|
||||||
|
// Initialize data for whatever languages are supported by our game/region
|
||||||
|
ERegion Region = ( Entry() && Entry()->Project() ? Entry()->Project()->Region() : ERegion::NTSC );
|
||||||
|
const std::vector<ELanguage>& kLanguageArray = GetSupportedLanguages(Game(), Region);
|
||||||
|
mLanguages.resize( kLanguageArray.size() );
|
||||||
|
|
||||||
|
for (uint i=0; i < kLanguageArray.size(); i++)
|
||||||
|
{
|
||||||
|
mLanguages[i].Language = kLanguageArray[i];
|
||||||
|
mLanguages[i].Strings.resize(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Serialize resource data */
|
||||||
|
void CStringTable::Serialize(IArchive& Arc)
|
||||||
|
{
|
||||||
|
Arc << SerialParameter("StringNames", mStringNames, SH_Optional)
|
||||||
|
<< SerialParameter("Languages", mLanguages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Build the dependency tree for this resource */
|
||||||
|
CDependencyTree* CStringTable::BuildDependencyTree() const
|
||||||
|
{
|
||||||
|
// STRGs can reference FONTs with the &font=; formatting tag and TXTRs with the &image=; tag
|
||||||
|
CDependencyTree* pTree = new CDependencyTree();
|
||||||
|
EIDLength IDLength = CAssetID::GameIDLength( Game() );
|
||||||
|
|
||||||
|
for (uint LanguageIdx = 0; LanguageIdx < mLanguages.size(); LanguageIdx++)
|
||||||
|
{
|
||||||
|
const SLanguageData& kLanguage = mLanguages[LanguageIdx];
|
||||||
|
|
||||||
|
for (uint StringIdx = 0; StringIdx < kLanguage.Strings.size(); StringIdx++)
|
||||||
|
{
|
||||||
|
const TString& kString = kLanguage.Strings[StringIdx].String;
|
||||||
|
|
||||||
|
for (int TagIdx = kString.IndexOf('&'); TagIdx != -1; TagIdx = kString.IndexOf('&', TagIdx + 1))
|
||||||
|
{
|
||||||
|
// Check for double ampersand (escape character in DKCR, not sure about other games)
|
||||||
|
if (kString.At(TagIdx + 1) == '&')
|
||||||
|
{
|
||||||
|
TagIdx++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get tag name and parameters
|
||||||
|
int NameEnd = kString.IndexOf('=', TagIdx);
|
||||||
|
int TagEnd = kString.IndexOf(';', TagIdx);
|
||||||
|
if (NameEnd == -1 || TagEnd == -1) continue;
|
||||||
|
|
||||||
|
TString TagName = kString.SubString(TagIdx + 1, NameEnd - TagIdx - 1);
|
||||||
|
TString ParamString = kString.SubString(NameEnd + 1, TagEnd - NameEnd - 1);
|
||||||
|
if (ParamString.IsEmpty()) continue;
|
||||||
|
|
||||||
|
// Font
|
||||||
|
if (TagName == "font")
|
||||||
|
{
|
||||||
|
if (Game() >= EGame::CorruptionProto)
|
||||||
|
{
|
||||||
|
ASSERT(ParamString.StartsWith("0x"));
|
||||||
|
ParamString = ParamString.ChopFront(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT(ParamString.Size() == IDLength * 2);
|
||||||
|
pTree->AddDependency( CAssetID::FromString(ParamString) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image
|
||||||
|
else if (TagName == "image")
|
||||||
|
{
|
||||||
|
// Determine which params are textures based on image type
|
||||||
|
TStringList Params = ParamString.Split(",");
|
||||||
|
TString ImageType = Params.front();
|
||||||
|
uint TexturesStart = 0;
|
||||||
|
|
||||||
|
if (ImageType == "A")
|
||||||
|
TexturesStart = 2;
|
||||||
|
|
||||||
|
else if (ImageType == "SI")
|
||||||
|
TexturesStart = 3;
|
||||||
|
|
||||||
|
else if (ImageType == "SA")
|
||||||
|
TexturesStart = 4;
|
||||||
|
|
||||||
|
else if (ImageType == "B")
|
||||||
|
TexturesStart = 2;
|
||||||
|
|
||||||
|
else if (ImageType.IsHexString(false, IDLength * 2))
|
||||||
|
TexturesStart = 0;
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errorf("Unrecognized image type: %s", *ImageType);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load texture IDs
|
||||||
|
TStringList::iterator Iter = Params.begin();
|
||||||
|
|
||||||
|
for (uint ParamIdx = 0; ParamIdx < Params.size(); ParamIdx++, Iter++)
|
||||||
|
{
|
||||||
|
if (ParamIdx >= TexturesStart)
|
||||||
|
{
|
||||||
|
TString Param = *Iter;
|
||||||
|
|
||||||
|
if (Game() >= EGame::CorruptionProto)
|
||||||
|
{
|
||||||
|
ASSERT(Param.StartsWith("0x"));
|
||||||
|
Param = Param.ChopFront(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT(Param.Size() == IDLength * 2);
|
||||||
|
pTree->AddDependency( CAssetID::FromString(Param) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Static - Strip all formatting tags for a given string */
|
||||||
|
TString CStringTable::StripFormatting(const TString& kInString)
|
||||||
|
{
|
||||||
|
TString Out = kInString;
|
||||||
|
int TagStart = -1;
|
||||||
|
|
||||||
|
for (uint CharIdx = 0; CharIdx < Out.Size(); CharIdx++)
|
||||||
|
{
|
||||||
|
if (Out[CharIdx] == '&')
|
||||||
|
{
|
||||||
|
if (TagStart == -1)
|
||||||
|
TagStart = CharIdx;
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Out.Remove(TagStart, 1);
|
||||||
|
TagStart = -1;
|
||||||
|
CharIdx--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (TagStart != -1 && Out[CharIdx] == ';')
|
||||||
|
{
|
||||||
|
int TagEnd = CharIdx + 1;
|
||||||
|
int TagLen = TagEnd - TagStart;
|
||||||
|
Out.Remove(TagStart, TagLen);
|
||||||
|
CharIdx = TagStart - 1;
|
||||||
|
TagStart = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Static - Returns whether a given language is supported by the given game/region combination */
|
||||||
|
bool CStringTable::IsLanguageSupported(ELanguage Language, EGame Game, ERegion Region)
|
||||||
|
{
|
||||||
|
const std::vector<ELanguage>& kLanguageArray = GetSupportedLanguages(Game, Region);
|
||||||
|
|
||||||
|
// Check if the requested language is in the array.
|
||||||
|
for (uint LanguageIdx = 0; LanguageIdx < kLanguageArray.size(); LanguageIdx++)
|
||||||
|
{
|
||||||
|
if (kLanguageArray[LanguageIdx] == Language)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsupported
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
#ifndef CSTRINGTABLE_H
|
||||||
|
#define CSTRINGTABLE_H
|
||||||
|
|
||||||
|
#include "ELanguage.h"
|
||||||
|
#include "Core/Resource/CResource.h"
|
||||||
|
#include <Common/BasicTypes.h>
|
||||||
|
#include <Common/CFourCC.h>
|
||||||
|
#include <Common/TString.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
/** A table of localized strings from STRG assets.
|
||||||
|
* Strings are always internally stored as UTF-8.
|
||||||
|
*/
|
||||||
|
class CStringTable : public CResource
|
||||||
|
{
|
||||||
|
DECLARE_RESOURCE_TYPE(StringTable)
|
||||||
|
friend class CStringLoader;
|
||||||
|
friend class CStringCooker;
|
||||||
|
|
||||||
|
/** List of string names. Optional data, can be empty. */
|
||||||
|
std::vector<TString> mStringNames;
|
||||||
|
|
||||||
|
/** String data for a language */
|
||||||
|
struct SStringData
|
||||||
|
{
|
||||||
|
TString String;
|
||||||
|
bool IsLocalized;
|
||||||
|
|
||||||
|
SStringData()
|
||||||
|
: IsLocalized(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void Serialize(IArchive& Arc)
|
||||||
|
{
|
||||||
|
Arc << SerialParameter("String", String)
|
||||||
|
<< SerialParameter("IsLocalized", IsLocalized, SH_Optional, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SLanguageData
|
||||||
|
{
|
||||||
|
ELanguage Language;
|
||||||
|
std::vector<SStringData> Strings;
|
||||||
|
|
||||||
|
void Serialize(IArchive& Arc)
|
||||||
|
{
|
||||||
|
Arc << SerialParameter("Language", Language)
|
||||||
|
<< SerialParameter("Strings", Strings);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
std::vector<SLanguageData> mLanguages;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Constructor */
|
||||||
|
CStringTable(CResourceEntry *pEntry = 0) : CResource(pEntry) {}
|
||||||
|
|
||||||
|
/** Returns the number of languages in the table */
|
||||||
|
inline uint NumLanguages() const { return mLanguages.size(); }
|
||||||
|
|
||||||
|
/** Returns the number of strings in the table */
|
||||||
|
inline uint NumStrings() const { return mLanguages.empty() ? 0 : mLanguages[0].Strings.size(); }
|
||||||
|
|
||||||
|
/** Returns languages used by index */
|
||||||
|
inline ELanguage LanguageByIndex(uint Index) const { return mLanguages.size() > Index ? mLanguages[Index].Language : ELanguage::Invalid; }
|
||||||
|
|
||||||
|
/** Returns the string name by string index. May be blank if the string at the requested index is unnamed */
|
||||||
|
inline TString StringNameByIndex(uint Index) const { return mStringNames.size() > Index ? mStringNames[Index] : ""; }
|
||||||
|
|
||||||
|
/** Returns a string given a language/index pair */
|
||||||
|
TString GetString(ELanguage Language, uint StringIndex) const;
|
||||||
|
|
||||||
|
/** Updates a string for a given language */
|
||||||
|
void SetString(ELanguage Language, uint StringIndex, const TString& kNewString);
|
||||||
|
|
||||||
|
/** Updates a string name */
|
||||||
|
void SetStringName(uint StringIndex, const TString& kNewName);
|
||||||
|
|
||||||
|
/** Move string to another position in the table */
|
||||||
|
void MoveString(uint StringIndex, uint NewIndex);
|
||||||
|
|
||||||
|
/** Add a new string to the table */
|
||||||
|
void AddString(uint AtIndex);
|
||||||
|
|
||||||
|
/** Remove a string from the table */
|
||||||
|
void RemoveString(uint StringIndex);
|
||||||
|
|
||||||
|
/** Initialize new resource data */
|
||||||
|
virtual void InitializeNewResource() override;
|
||||||
|
|
||||||
|
/** Serialize resource data */
|
||||||
|
virtual void Serialize(IArchive& Arc) override;
|
||||||
|
|
||||||
|
/** Build the dependency tree for this resource */
|
||||||
|
virtual CDependencyTree* BuildDependencyTree() const override;
|
||||||
|
|
||||||
|
/** Static - Strip all formatting tags for a given string */
|
||||||
|
static TString StripFormatting(const TString& kInString);
|
||||||
|
|
||||||
|
/** Static - Returns whether a given language is supported by the given game/region combination */
|
||||||
|
static bool IsLanguageSupported(ELanguage Language, EGame Game, ERegion Region);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CSTRINGTABLE_H
|
|
@ -0,0 +1,29 @@
|
||||||
|
#ifndef ELANGUAGE_H
|
||||||
|
#define ELANGUAGE_H
|
||||||
|
|
||||||
|
#include <Common/CFourCC.h>
|
||||||
|
|
||||||
|
/** A language in the game's localization system */
|
||||||
|
enum class ELanguage
|
||||||
|
{
|
||||||
|
// The original release of Metroid Prime only supported English
|
||||||
|
English = FOURCC('ENGL'),
|
||||||
|
// Support for these languages was added in the PAL version of Metroid Prime
|
||||||
|
German = FOURCC('GERM'),
|
||||||
|
French = FOURCC('FREN'),
|
||||||
|
Spanish = FOURCC('SPAN'),
|
||||||
|
Italian = FOURCC('ITAL'),
|
||||||
|
Dutch = FOURCC('DUTC'), // Unused
|
||||||
|
Japanese = FOURCC('JAPN'),
|
||||||
|
// The rest of these languages were added in Donkey Kong Country Returns
|
||||||
|
SimplifiedChinese = FOURCC('SCHN'), // Unused
|
||||||
|
TraditionalChinese = FOURCC('TCHN'), // Unused
|
||||||
|
UKEnglish = FOURCC('UKEN'),
|
||||||
|
Korean = FOURCC('KORE'),
|
||||||
|
NAFrench = FOURCC('NAFR'),
|
||||||
|
NASpanish = FOURCC('NASP'),
|
||||||
|
// Invalid
|
||||||
|
Invalid = FOURCC('INVD')
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ELANGUAGE_H
|
|
@ -1,5 +1,6 @@
|
||||||
#include "CPointOfInterestExtra.h"
|
#include "CPointOfInterestExtra.h"
|
||||||
|
|
||||||
|
//@todo pull these values from tweaks instead of hardcoding them
|
||||||
const CColor CPointOfInterestExtra::skRegularColor = CColor::Integral(0xFF,0x70,0x00);
|
const CColor CPointOfInterestExtra::skRegularColor = CColor::Integral(0xFF,0x70,0x00);
|
||||||
const CColor CPointOfInterestExtra::skImportantColor = CColor::Integral(0xFF,0x00,0x00);
|
const CColor CPointOfInterestExtra::skImportantColor = CColor::Integral(0xFF,0x00,0x00);
|
||||||
|
|
||||||
|
@ -19,14 +20,17 @@ CPointOfInterestExtra::CPointOfInterestExtra(CScriptObject *pInstance, CScene *p
|
||||||
void CPointOfInterestExtra::PropertyModified(IProperty* pProperty)
|
void CPointOfInterestExtra::PropertyModified(IProperty* pProperty)
|
||||||
{
|
{
|
||||||
if (mScanProperty.Property() == pProperty)
|
if (mScanProperty.Property() == pProperty)
|
||||||
|
{
|
||||||
mpScanData = gpResourceStore->LoadResource<CScan>( mScanProperty.Get() );
|
mpScanData = gpResourceStore->LoadResource<CScan>( mScanProperty.Get() );
|
||||||
|
mScanIsCritical = (mpScanData ? mpScanData->IsCriticalPropertyRef() : CBoolRef());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPointOfInterestExtra::ModifyTintColor(CColor& Color)
|
void CPointOfInterestExtra::ModifyTintColor(CColor& Color)
|
||||||
{
|
{
|
||||||
if (mpScanData)
|
if (mpScanData)
|
||||||
{
|
{
|
||||||
if (mpScanData->IsImportant()) Color *= skImportantColor;
|
if (mScanIsCritical) Color *= skImportantColor;
|
||||||
else Color *= skRegularColor;
|
else Color *= skRegularColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#define CPOINTOFINTERESTEXTRA_H
|
#define CPOINTOFINTERESTEXTRA_H
|
||||||
|
|
||||||
#include "CScriptExtra.h"
|
#include "CScriptExtra.h"
|
||||||
#include "Core/Resource/CScan.h"
|
#include "Core/Resource/Scan/CScan.h"
|
||||||
#include <Common/CColor.h>
|
#include <Common/CColor.h>
|
||||||
|
|
||||||
class CPointOfInterestExtra : public CScriptExtra
|
class CPointOfInterestExtra : public CScriptExtra
|
||||||
|
@ -10,6 +10,7 @@ class CPointOfInterestExtra : public CScriptExtra
|
||||||
// Tint POI billboard orange/red depending on scan importance
|
// Tint POI billboard orange/red depending on scan importance
|
||||||
CAssetRef mScanProperty;
|
CAssetRef mScanProperty;
|
||||||
TResPtr<CScan> mpScanData;
|
TResPtr<CScan> mpScanData;
|
||||||
|
CBoolRef mScanIsCritical;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CPointOfInterestExtra(CScriptObject *pInstance, CScene *pScene, CScriptNode *pParent = 0);
|
explicit CPointOfInterestExtra(CScriptObject *pInstance, CScene *pScene, CScriptNode *pParent = 0);
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
#include "CTweakCooker.h"
|
||||||
|
#include "Core/Resource/Cooker/CScriptCooker.h"
|
||||||
|
|
||||||
|
/** Cooker entry point */
|
||||||
|
bool CTweakCooker::CookCTWK(CTweakData* pTweakData, IOutputStream& CTWK)
|
||||||
|
{
|
||||||
|
CStructRef TweakProperties = pTweakData->TweakData();
|
||||||
|
CScriptCooker ScriptCooker(pTweakData->Game());
|
||||||
|
ScriptCooker.WriteProperty(CTWK, TweakProperties.Property(), TweakProperties.DataPointer(), true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CTweakCooker::CookNTWK(const std::vector<CTweakData*>& kTweaks, IOutputStream& NTWK)
|
||||||
|
{
|
||||||
|
NTWK.WriteFourCC( FOURCC('NTWK') ); // NTWK magic
|
||||||
|
NTWK.WriteByte( 1 ); // Version number; must be 1
|
||||||
|
NTWK.WriteLong( kTweaks.size() ); // Number of tweak objects
|
||||||
|
|
||||||
|
for (uint TweakIdx = 0; TweakIdx < kTweaks.size(); TweakIdx++)
|
||||||
|
{
|
||||||
|
CTweakData* pTweakData = kTweaks[TweakIdx];
|
||||||
|
|
||||||
|
// Tweaks in MP2+ are saved with the script object data format
|
||||||
|
// Write a dummy script object header here
|
||||||
|
uint TweakObjectStart = NTWK.Tell();
|
||||||
|
NTWK.WriteLong( pTweakData->TweakID() ); // Object ID
|
||||||
|
NTWK.WriteShort( 0 ); // Object size
|
||||||
|
NTWK.WriteLong( TweakIdx ); // Instance ID
|
||||||
|
NTWK.WriteShort( 0 ); // Link count
|
||||||
|
|
||||||
|
CStructRef TweakProperties = pTweakData->TweakData();
|
||||||
|
CScriptCooker ScriptCooker(TweakProperties.Property()->Game());
|
||||||
|
ScriptCooker.WriteProperty(NTWK, TweakProperties.Property(), TweakProperties.DataPointer(), false);
|
||||||
|
|
||||||
|
uint TweakObjectEnd = NTWK.Tell();
|
||||||
|
uint TweakObjectSize = (uint16) (TweakObjectEnd - TweakObjectStart - 6);
|
||||||
|
NTWK.GoTo(TweakObjectStart + 4);
|
||||||
|
NTWK.WriteShort(TweakObjectSize);
|
||||||
|
NTWK.GoTo(TweakObjectEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
NTWK.WriteToBoundary(32, 0);
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef CTWEAKCOOKER_H
|
||||||
|
#define CTWEAKCOOKER_H
|
||||||
|
|
||||||
|
#include "CTweakData.h"
|
||||||
|
|
||||||
|
/** Class responsible for cooking tweak data */
|
||||||
|
class CTweakCooker
|
||||||
|
{
|
||||||
|
/** Private constructor */
|
||||||
|
CTweakCooker() {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Cooker entry point */
|
||||||
|
static bool CookCTWK(CTweakData* pTweakData, IOutputStream& CTWK);
|
||||||
|
static bool CookNTWK(const std::vector<CTweakData*>& kTweaks, IOutputStream& NTWK);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CTWEAKCOOKER_H
|
|
@ -0,0 +1,62 @@
|
||||||
|
#ifndef CTWEAKDATA_H
|
||||||
|
#define CTWEAKDATA_H
|
||||||
|
|
||||||
|
#include "Core/Resource/CResource.h"
|
||||||
|
#include "Core/Resource/Script/CScriptTemplate.h"
|
||||||
|
#include "Core/Resource/Script/Property/TPropertyRef.h"
|
||||||
|
|
||||||
|
/** Tweak data assets for MP1 */
|
||||||
|
class CTweakData : public CResource
|
||||||
|
{
|
||||||
|
DECLARE_RESOURCE_TYPE(Tweaks)
|
||||||
|
|
||||||
|
/** Script template specifying tweak data layout */
|
||||||
|
CScriptTemplate* mpTemplate;
|
||||||
|
|
||||||
|
/** Tweak ID for MP2+ */
|
||||||
|
uint mTweakID;
|
||||||
|
|
||||||
|
/** Tweak data */
|
||||||
|
std::vector<uint8> mTweakData;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CTweakData(CScriptTemplate* pTemplate, uint TweakID, CResourceEntry* pEntry = 0)
|
||||||
|
: mpTemplate(pTemplate)
|
||||||
|
, mTweakID(TweakID)
|
||||||
|
, CResource(pEntry)
|
||||||
|
{
|
||||||
|
CStructProperty* pProperties = pTemplate->Properties();
|
||||||
|
mTweakData.resize(pProperties->DataSize());
|
||||||
|
pProperties->Construct(mTweakData.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
TString TweakName()
|
||||||
|
{
|
||||||
|
if (Entry() != nullptr)
|
||||||
|
{
|
||||||
|
return Entry()->Name();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IProperty* pNameProperty = mpTemplate->Properties()->ChildByID(0x7FDA1466);
|
||||||
|
return CStringRef(mTweakData.data(), pNameProperty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline CScriptTemplate* TweakTemplate() const
|
||||||
|
{
|
||||||
|
return mpTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint32 TweakID() const
|
||||||
|
{
|
||||||
|
return mTweakID;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline CStructRef TweakData() const
|
||||||
|
{
|
||||||
|
return CStructRef((void*) mTweakData.data(), mpTemplate->Properties());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CTWEAKDATA_H
|
|
@ -0,0 +1,130 @@
|
||||||
|
#include "CTweakLoader.h"
|
||||||
|
#include "Core/Resource/Factory/CScriptLoader.h"
|
||||||
|
#include "Core/Resource/Script/NGameList.h"
|
||||||
|
|
||||||
|
CTweakData* CTweakLoader::LoadCTWK(IInputStream& CTWK, CResourceEntry* pEntry)
|
||||||
|
{
|
||||||
|
// Find the correct template based on the asset ID.
|
||||||
|
static const std::unordered_map<uint, const char*> skIdToTemplateName =
|
||||||
|
{
|
||||||
|
{ 0x1D180D7C, "TweakParticle" },
|
||||||
|
{ 0x264A4972, "TweakPlayer" },
|
||||||
|
{ 0x33B3323A, "TweakGunRes" },
|
||||||
|
{ 0x39AD28D3, "TweakCameraBob" },
|
||||||
|
{ 0x3FAEC012, "TweakPlayerControls", },
|
||||||
|
{ 0x5ED56350, "TweakBall", },
|
||||||
|
{ 0x5F24EFF8, "TweakSlideShow", },
|
||||||
|
{ 0x6907A32D, "TweakPlayerGun", },
|
||||||
|
{ 0x85CA11E9, "TweakPlayerRes", },
|
||||||
|
{ 0x94C76ECD, "TweakTargeting", },
|
||||||
|
{ 0x953A7C63, "TweakGame", },
|
||||||
|
{ 0xC9954E56, "TweakGuiColors", },
|
||||||
|
{ 0xE66A4F86, "TweakAutoMapper", },
|
||||||
|
{ 0xED2E48A9, "TweakGui", },
|
||||||
|
{ 0xF1ED8FD7, "TweakPlayerControls", }
|
||||||
|
};
|
||||||
|
|
||||||
|
auto Find = skIdToTemplateName.find( pEntry->ID().ToLong() );
|
||||||
|
ASSERT( Find != skIdToTemplateName.end() );
|
||||||
|
const char* pkTemplateName = Find->second;
|
||||||
|
|
||||||
|
// Fetch template
|
||||||
|
CGameTemplate* pGameTemplate = NGameList::GetGameTemplate( pEntry->Game() );
|
||||||
|
ASSERT( pGameTemplate != nullptr );
|
||||||
|
|
||||||
|
CScriptTemplate* pTweakTemplate = pGameTemplate->FindMiscTemplate(pkTemplateName);
|
||||||
|
ASSERT( pTweakTemplate != nullptr );
|
||||||
|
|
||||||
|
// Load tweak data
|
||||||
|
CTweakData* pTweakData = new CTweakData(pTweakTemplate, pEntry->ID().ToLong(), pEntry);
|
||||||
|
CScriptLoader::LoadStructData( CTWK, pTweakData->TweakData() );
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
if (!CTWK.EoF() && CTWK.PeekShort() != -1)
|
||||||
|
{
|
||||||
|
errorf("%s: unread property data, tweak template may be malformed (%d bytes left)", *CTWK.GetSourceString(), CTWK.Size() - CTWK.Tell());
|
||||||
|
delete pTweakData;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pTweakData;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTweakLoader::LoadNTWK(IInputStream& NTWK, EGame Game, std::vector<CTweakData*>& OutTweaks)
|
||||||
|
{
|
||||||
|
// Validate file. NTWK basically embeds a bunch of tweak objects using the script layers
|
||||||
|
// format, so it has the same version byte that script layers have.
|
||||||
|
uint Magic = NTWK.ReadLong();
|
||||||
|
uint8 LayerVersion = NTWK.ReadByte();
|
||||||
|
|
||||||
|
if (Magic != FOURCC('NTWK'))
|
||||||
|
{
|
||||||
|
errorf("Unrecognized NTWK magic: 0x%08X", Magic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (LayerVersion != 1)
|
||||||
|
{
|
||||||
|
errorf("Unrecognized layer version in NTWK: %d", LayerVersion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGameTemplate* pGameTemplate = NGameList::GetGameTemplate( Game );
|
||||||
|
ASSERT( pGameTemplate != nullptr );
|
||||||
|
|
||||||
|
// Start reading tweaks
|
||||||
|
uint NumTweaks = NTWK.ReadLong();
|
||||||
|
|
||||||
|
for (uint TweakIdx = 0; TweakIdx < NumTweaks; TweakIdx++)
|
||||||
|
{
|
||||||
|
// Find the correct template based on the tweak ID.
|
||||||
|
static const std::unordered_map<uint, const char*> skIdToTemplateName =
|
||||||
|
{
|
||||||
|
{ FOURCC('TWAC'), "TweakAdvancedControls" },
|
||||||
|
{ FOURCC('TWAM'), "TweakAutoMapper" },
|
||||||
|
{ FOURCC('TWBL'), "TweakBall" },
|
||||||
|
{ FOURCC('TWC2'), "TweakPlayerControls" },
|
||||||
|
{ FOURCC('TWCB'), "TweakCameraBob" },
|
||||||
|
{ FOURCC('TWCC'), "TweakGamecubeControls" },
|
||||||
|
{ FOURCC('TWCT'), "TweakControls" },
|
||||||
|
{ FOURCC('TWEC'), "TweakExpertControls" },
|
||||||
|
{ FOURCC('TWGM'), "TweakGame" },
|
||||||
|
{ FOURCC('TWGT'), "TweakGraphicalTransitions" },
|
||||||
|
{ FOURCC('TWGU'), "TweakGui" },
|
||||||
|
{ FOURCC('TWGC'), "TweakGuiColors" },
|
||||||
|
{ FOURCC('TWP2'), "TweakPlayer" },
|
||||||
|
{ FOURCC('TWPC'), "TweakPlayerControls" },
|
||||||
|
{ FOURCC('TWPG'), "TweakPlayerGun" },
|
||||||
|
{ FOURCC('TWPL'), "TweakPlayer" },
|
||||||
|
{ FOURCC('TWPM'), "TweakPlayerGun" },
|
||||||
|
{ FOURCC('TWPA'), "TweakParticle" },
|
||||||
|
{ FOURCC('TWPR'), "TweakPlayerRes" },
|
||||||
|
{ FOURCC('TWRC'), "TweakRevolutionControls" },
|
||||||
|
{ FOURCC('TWSS'), "TweakSlideShow" },
|
||||||
|
{ FOURCC('TWTG'), "TweakTargeting" },
|
||||||
|
};
|
||||||
|
|
||||||
|
uint TweakID = NTWK.ReadLong();
|
||||||
|
uint16 TweakSize = NTWK.ReadShort();
|
||||||
|
uint NextTweak = NTWK.Tell() + TweakSize;
|
||||||
|
|
||||||
|
auto Find = skIdToTemplateName.find(TweakID);
|
||||||
|
|
||||||
|
if (Find == skIdToTemplateName.end())
|
||||||
|
{
|
||||||
|
errorf("Unrecognized tweak ID: %s (0x%08X)", *CFourCC(TweakID).ToString(), TweakID);
|
||||||
|
NTWK.GoTo(NextTweak);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
CScriptTemplate* pTweakTemplate = pGameTemplate->FindMiscTemplate( Find->second );
|
||||||
|
ASSERT( pTweakTemplate != nullptr );
|
||||||
|
|
||||||
|
// Load tweak data
|
||||||
|
NTWK.Skip(0xC);
|
||||||
|
CTweakData* pTweakData = new CTweakData(pTweakTemplate, TweakID);
|
||||||
|
CScriptLoader::LoadStructData( NTWK, pTweakData->TweakData() );
|
||||||
|
OutTweaks.push_back(pTweakData);
|
||||||
|
|
||||||
|
NTWK.GoTo(NextTweak);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef CTWEAKLOADER_H
|
||||||
|
#define CTWEAKLOADER_H
|
||||||
|
|
||||||
|
#include "CTweakData.h"
|
||||||
|
|
||||||
|
/** Class responsible for loading tweak data */
|
||||||
|
class CTweakLoader
|
||||||
|
{
|
||||||
|
/** Private constructor */
|
||||||
|
CTweakLoader() {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Loader entry point */
|
||||||
|
static CTweakData* LoadCTWK(IInputStream& CTWK, CResourceEntry* pEntry);
|
||||||
|
static void LoadNTWK(IInputStream& NTWK, EGame Game, std::vector<CTweakData*>& OutTweaks);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CTWEAKLOADER_H
|
|
@ -0,0 +1,90 @@
|
||||||
|
#include "CTweakManager.h"
|
||||||
|
#include "Core/GameProject/CGameProject.h"
|
||||||
|
#include "Core/GameProject/CResourceIterator.h"
|
||||||
|
#include "Core/Tweaks/CTweakLoader.h"
|
||||||
|
#include "Core/Tweaks/CTweakCooker.h"
|
||||||
|
|
||||||
|
CTweakManager::CTweakManager(CGameProject* pInProject)
|
||||||
|
: mpProject(pInProject)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CTweakManager::~CTweakManager()
|
||||||
|
{
|
||||||
|
ClearTweaks();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTweakManager::LoadTweaks()
|
||||||
|
{
|
||||||
|
ASSERT( mTweakObjects.empty() );
|
||||||
|
|
||||||
|
// MP1 - Load all tweak assets into memory
|
||||||
|
if (mpProject->Game() <= EGame::Prime)
|
||||||
|
{
|
||||||
|
for (TResourceIterator<EResourceType::Tweaks> It(mpProject->ResourceStore()); It; ++It)
|
||||||
|
{
|
||||||
|
CTweakData* pTweaks = (CTweakData*) It->Load();
|
||||||
|
pTweaks->Lock();
|
||||||
|
mTweakObjects.push_back(pTweaks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MP2+ - Load tweaks from Standard.ntwk
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TString FilePath = mpProject->DiscFilesystemRoot(false) + "Standard.ntwk";
|
||||||
|
CFileInStream StandardNTWK(FilePath, EEndian::BigEndian);
|
||||||
|
CTweakLoader::LoadNTWK(StandardNTWK, mpProject->Game(), mTweakObjects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CTweakManager::SaveTweaks()
|
||||||
|
{
|
||||||
|
// MP1 - Save all tweak assets
|
||||||
|
if (mpProject->Game() <= EGame::Prime)
|
||||||
|
{
|
||||||
|
bool SavedAll = true, SavedAny = false;
|
||||||
|
|
||||||
|
for (CTweakData* pTweakData : mTweakObjects)
|
||||||
|
{
|
||||||
|
if (!pTweakData->Entry()->Save(true))
|
||||||
|
{
|
||||||
|
SavedAll = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SavedAny = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SavedAny)
|
||||||
|
{
|
||||||
|
mpProject->ResourceStore()->ConditionalSaveStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
return SavedAll;
|
||||||
|
}
|
||||||
|
// MP2+ - Save tweaks to Standard.ntwk
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TString FilePath = mpProject->DiscFilesystemRoot(false) + "Standard.ntwk";
|
||||||
|
CFileOutStream StandardNTWK(FilePath, EEndian::BigEndian);
|
||||||
|
return CTweakCooker::CookNTWK(mTweakObjects, StandardNTWK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTweakManager::ClearTweaks()
|
||||||
|
{
|
||||||
|
for (CTweakData* pTweakData : mTweakObjects)
|
||||||
|
{
|
||||||
|
if (pTweakData->Entry() != nullptr)
|
||||||
|
{
|
||||||
|
pTweakData->Release();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delete pTweakData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mTweakObjects.clear();
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
#ifndef CTWEAKMANAGER_H
|
||||||
|
#define CTWEAKMANAGER_H
|
||||||
|
|
||||||
|
#include "CTweakData.h"
|
||||||
|
|
||||||
|
/** Class responsible for managing game tweak data, including saving/loading and providing access */
|
||||||
|
class CTweakManager
|
||||||
|
{
|
||||||
|
/** Project */
|
||||||
|
CGameProject* mpProject;
|
||||||
|
|
||||||
|
/** All tweak resources in the current game */
|
||||||
|
std::vector< CTweakData* > mTweakObjects;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CTweakManager(CGameProject* pInProject);
|
||||||
|
~CTweakManager();
|
||||||
|
void LoadTweaks();
|
||||||
|
bool SaveTweaks();
|
||||||
|
void ClearTweaks();
|
||||||
|
|
||||||
|
// Accessors
|
||||||
|
inline const std::vector<CTweakData*>& TweakObjects() const
|
||||||
|
{
|
||||||
|
return mTweakObjects;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CTWEAKMANAGER_H
|
|
@ -0,0 +1,59 @@
|
||||||
|
#ifndef CCUSTOMDELEGATE_H
|
||||||
|
#define CCUSTOMDELEGATE_H
|
||||||
|
|
||||||
|
#include <QFont>
|
||||||
|
#include <QFontMetrics>
|
||||||
|
#include <QPen>
|
||||||
|
#include <QStyledItemDelegate>
|
||||||
|
|
||||||
|
/** Font parameters for rendering text */
|
||||||
|
struct SDelegateFontInfo
|
||||||
|
{
|
||||||
|
QFont NameFont;
|
||||||
|
QFont InfoFont;
|
||||||
|
QFontMetrics NameFontMetrics;
|
||||||
|
QFontMetrics InfoFontMetrics;
|
||||||
|
QPen NamePen;
|
||||||
|
QPen InfoPen;
|
||||||
|
int Margin;
|
||||||
|
int Spacing;
|
||||||
|
|
||||||
|
SDelegateFontInfo()
|
||||||
|
: NameFontMetrics(NameFont), InfoFontMetrics(InfoFont) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Common base class of custom item delegate implementations */
|
||||||
|
class CCustomDelegate : public QStyledItemDelegate
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CCustomDelegate(QObject* pParent = 0)
|
||||||
|
: QStyledItemDelegate(pParent)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual SDelegateFontInfo GetFontInfo(const QStyleOptionViewItem& rkOption) const
|
||||||
|
{
|
||||||
|
SDelegateFontInfo Info;
|
||||||
|
|
||||||
|
Info.NameFont = rkOption.font;
|
||||||
|
Info.NameFont.setPointSize( rkOption.font.pointSize() + 1 );
|
||||||
|
Info.NameFontMetrics = QFontMetrics(Info.NameFont);
|
||||||
|
|
||||||
|
Info.InfoFont = rkOption.font;
|
||||||
|
Info.InfoFont.setPointSize( rkOption.font.pointSize() - 1 );
|
||||||
|
Info.InfoFontMetrics = QFontMetrics(Info.InfoFont);
|
||||||
|
|
||||||
|
Info.NamePen = QPen(rkOption.palette.text(), 1.f);
|
||||||
|
|
||||||
|
Info.InfoPen = QPen(rkOption.palette.text(), 1.f);
|
||||||
|
Info.InfoPen.setColor( Info.InfoPen.color().darker(140) );
|
||||||
|
|
||||||
|
Info.Margin = 3;
|
||||||
|
Info.Spacing = 3;
|
||||||
|
|
||||||
|
return Info;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CCUSTOMDELEGATE_H
|
|
@ -5,6 +5,8 @@
|
||||||
#include "CProjectSettingsDialog.h"
|
#include "CProjectSettingsDialog.h"
|
||||||
#include "Editor/CharacterEditor/CCharacterEditor.h"
|
#include "Editor/CharacterEditor/CCharacterEditor.h"
|
||||||
#include "Editor/ModelEditor/CModelEditorWindow.h"
|
#include "Editor/ModelEditor/CModelEditorWindow.h"
|
||||||
|
#include "Editor/ScanEditor/CScanEditor.h"
|
||||||
|
#include "Editor/StringEditor/CStringEditor.h"
|
||||||
#include "Editor/ResourceBrowser/CResourceBrowser.h"
|
#include "Editor/ResourceBrowser/CResourceBrowser.h"
|
||||||
#include "Editor/WorldEditor/CWorldEditor.h"
|
#include "Editor/WorldEditor/CWorldEditor.h"
|
||||||
#include <Common/Macros.h>
|
#include <Common/Macros.h>
|
||||||
|
@ -19,6 +21,7 @@ CEditorApplication::CEditorApplication(int& rArgc, char **ppArgv)
|
||||||
, mpActiveProject(nullptr)
|
, mpActiveProject(nullptr)
|
||||||
, mpWorldEditor(nullptr)
|
, mpWorldEditor(nullptr)
|
||||||
, mpProjectDialog(nullptr)
|
, mpProjectDialog(nullptr)
|
||||||
|
, mInitialized(false)
|
||||||
{
|
{
|
||||||
mLastUpdate = CTimer::GlobalTime();
|
mLastUpdate = CTimer::GlobalTime();
|
||||||
|
|
||||||
|
@ -38,10 +41,14 @@ void CEditorApplication::InitEditor()
|
||||||
mpWorldEditor = new CWorldEditor();
|
mpWorldEditor = new CWorldEditor();
|
||||||
mpProjectDialog = new CProjectSettingsDialog(mpWorldEditor);
|
mpProjectDialog = new CProjectSettingsDialog(mpWorldEditor);
|
||||||
mpWorldEditor->showMaximized();
|
mpWorldEditor->showMaximized();
|
||||||
|
mInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEditorApplication::CloseAllEditors()
|
bool CEditorApplication::CloseAllEditors()
|
||||||
{
|
{
|
||||||
|
if (!mInitialized)
|
||||||
|
return true;
|
||||||
|
|
||||||
// Close active editor windows.
|
// Close active editor windows.
|
||||||
foreach (IEditor *pEditor, mEditorWindows)
|
foreach (IEditor *pEditor, mEditorWindows)
|
||||||
{
|
{
|
||||||
|
@ -152,11 +159,30 @@ void CEditorApplication::EditResource(CResourceEntry *pEntry)
|
||||||
case EResourceType::AnimSet:
|
case EResourceType::AnimSet:
|
||||||
pEd = new CCharacterEditor((CAnimSet*) pRes, mpWorldEditor);
|
pEd = new CCharacterEditor((CAnimSet*) pRes, mpWorldEditor);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case EResourceType::Scan:
|
||||||
|
pEd = new CScanEditor((CScan*) pRes, mpWorldEditor);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EResourceType::StringTable:
|
||||||
|
pEd = new CStringEditor((CStringTable*) pRes, mpWorldEditor);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EResourceType::Tweaks:
|
||||||
|
{
|
||||||
|
CTweakEditor* pTweakEditor = mpWorldEditor->TweakEditor();
|
||||||
|
pTweakEditor->SetActiveTweakData( (CTweakData*) pRes );
|
||||||
|
pEd = pTweakEditor;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pEd)
|
if (pEd)
|
||||||
{
|
{
|
||||||
pEd->show();
|
pEd->show();
|
||||||
|
|
||||||
|
if (pEntry->ResourceType() != EResourceType::Tweaks)
|
||||||
mEditingMap[pEntry] = pEd;
|
mEditingMap[pEntry] = pEd;
|
||||||
}
|
}
|
||||||
else if (pEntry->ResourceType() != EResourceType::Area)
|
else if (pEntry->ResourceType() != EResourceType::Area)
|
||||||
|
@ -223,6 +249,7 @@ bool CEditorApplication::RebuildResourceDatabase()
|
||||||
{
|
{
|
||||||
// Fake-close the project, but keep it in memory so we can modify the resource store
|
// Fake-close the project, but keep it in memory so we can modify the resource store
|
||||||
CGameProject *pProj = mpActiveProject;
|
CGameProject *pProj = mpActiveProject;
|
||||||
|
mpActiveProject->TweakManager()->ClearTweaks();
|
||||||
mpActiveProject = nullptr;
|
mpActiveProject = nullptr;
|
||||||
emit ActiveProjectChanged(nullptr);
|
emit ActiveProjectChanged(nullptr);
|
||||||
|
|
||||||
|
@ -237,6 +264,7 @@ bool CEditorApplication::RebuildResourceDatabase()
|
||||||
|
|
||||||
// Set project to active again
|
// Set project to active again
|
||||||
mpActiveProject = pProj;
|
mpActiveProject = pProj;
|
||||||
|
mpActiveProject->TweakManager()->LoadTweaks();
|
||||||
emit ActiveProjectChanged(pProj);
|
emit ActiveProjectChanged(pProj);
|
||||||
|
|
||||||
UICommon::InfoMsg(mpWorldEditor, "Success", "Resource database rebuilt successfully!");
|
UICommon::InfoMsg(mpWorldEditor, "Success", "Resource database rebuilt successfully!");
|
||||||
|
@ -303,7 +331,11 @@ void CEditorApplication::OnEditorClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
mEditorWindows.removeOne(pEditor);
|
mEditorWindows.removeOne(pEditor);
|
||||||
|
|
||||||
|
if (pEditor != mpWorldEditor->TweakEditor())
|
||||||
|
{
|
||||||
delete pEditor;
|
delete pEditor;
|
||||||
|
}
|
||||||
|
|
||||||
if (mpActiveProject)
|
if (mpActiveProject)
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,6 +25,7 @@ class CEditorApplication : public QApplication
|
||||||
CProjectSettingsDialog *mpProjectDialog;
|
CProjectSettingsDialog *mpProjectDialog;
|
||||||
QVector<IEditor*> mEditorWindows;
|
QVector<IEditor*> mEditorWindows;
|
||||||
QMap<CResourceEntry*,IEditor*> mEditingMap;
|
QMap<CResourceEntry*,IEditor*> mEditingMap;
|
||||||
|
bool mInitialized;
|
||||||
|
|
||||||
QTimer mRefreshTimer;
|
QTimer mRefreshTimer;
|
||||||
double mLastUpdate;
|
double mLastUpdate;
|
||||||
|
|
|
@ -33,8 +33,7 @@ CExportGameDialog::CExportGameDialog(const QString& rkIsoPath, const QString& rk
|
||||||
mpUI->setupUi(this);
|
mpUI->setupUi(this);
|
||||||
|
|
||||||
// Set up disc
|
// Set up disc
|
||||||
TWideString StrPath = TO_TWIDESTRING(rkIsoPath);
|
mpDisc = nod::OpenDiscFromImage(TO_WCHAR(rkIsoPath)).release();
|
||||||
mpDisc = nod::OpenDiscFromImage(*StrPath).release();
|
|
||||||
|
|
||||||
if (ValidateGame())
|
if (ValidateGame())
|
||||||
{
|
{
|
||||||
|
@ -151,12 +150,32 @@ bool CExportGameDialog::ValidateGame()
|
||||||
// This ID is normally MP1, but it's used by the MP1 NTSC demo and the MP2 bonus disc demo as well
|
// This ID is normally MP1, but it's used by the MP1 NTSC demo and the MP2 bonus disc demo as well
|
||||||
if (strcmp(rkHeader.m_gameTitle, "Long Game Name") == 0)
|
if (strcmp(rkHeader.m_gameTitle, "Long Game Name") == 0)
|
||||||
{
|
{
|
||||||
// todo - not handling demos yet
|
// Calculate the CRC of the apploader to figure out which game this is.
|
||||||
return false;
|
std::unique_ptr<uint8_t[]> pApploaderData = mpDisc->getDataPartition()->getApploaderBuf();
|
||||||
|
uint ApploaderSize = (uint) mpDisc->getDataPartition()->getApploaderSize();
|
||||||
|
uint ApploaderHash = CCRC32::StaticHashData(pApploaderData.get(), ApploaderSize);
|
||||||
|
|
||||||
|
if (ApploaderHash == 0x21B7AFF5)
|
||||||
|
{
|
||||||
|
// This is the hash for the NTSC MP1 demo.
|
||||||
|
mGame = EGame::PrimeDemo;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Hash is different, so this is most likely an Echoes demo build
|
||||||
|
mGame = EGame::EchoesDemo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This could be either Metroid Prime, or the PAL demo of it...
|
||||||
|
// In either case, the PAL demo is based on a later build of the game than the NTSC demo
|
||||||
|
// So the PAL demo should be configured the same way as the release build of the game anyway
|
||||||
mGame = EGame::Prime;
|
mGame = EGame::Prime;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case FOURCC('G2MX'):
|
case FOURCC('G2MX'):
|
||||||
// Echoes, but also appears in the MP3 proto
|
// Echoes, but also appears in the MP3 proto
|
||||||
|
@ -203,6 +222,16 @@ bool CExportGameDialog::ValidateGame()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The demo builds are not supported. The MP1 demo does not have script templates currently.
|
||||||
|
// Additionally, a lot of file format loaders currently don't support the demo variants of the
|
||||||
|
// file formats, meaning that attempting to export results in crashes.
|
||||||
|
if (mGame == EGame::PrimeDemo || mGame == EGame::EchoesDemo || mGame == EGame::CorruptionProto)
|
||||||
|
{
|
||||||
|
// we cannot parent the error message box to ourselves because this window hasn't been shown
|
||||||
|
UICommon::ErrorMsg(parentWidget(), "The demo builds are currently not supported.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -164,7 +164,7 @@ void CProjectSettingsDialog::BuildISO()
|
||||||
|
|
||||||
// Verify this ISO matches the original
|
// Verify this ISO matches the original
|
||||||
bool IsWii;
|
bool IsWii;
|
||||||
pBaseDisc = nod::OpenDiscFromImage(*TO_TWIDESTRING(SourceIsoPath), IsWii);
|
pBaseDisc = nod::OpenDiscFromImage(TO_WCHAR(SourceIsoPath), IsWii);
|
||||||
|
|
||||||
if (!pBaseDisc || !IsWii)
|
if (!pBaseDisc || !IsWii)
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
#include "CTweakEditor.h"
|
||||||
|
#include "ui_CTweakEditor.h"
|
||||||
|
#include "Editor/Undo/IUndoCommand.h"
|
||||||
|
|
||||||
|
/** Internal undo command for changing tabs */
|
||||||
|
class CSetTweakIndexCommand : public IUndoCommand
|
||||||
|
{
|
||||||
|
CTweakEditor* mpEditor;
|
||||||
|
int mOldIndex, mNewIndex;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CSetTweakIndexCommand(CTweakEditor* pEditor, int OldIndex, int NewIndex)
|
||||||
|
: IUndoCommand("Change Tab")
|
||||||
|
, mpEditor(pEditor)
|
||||||
|
, mOldIndex(OldIndex)
|
||||||
|
, mNewIndex(NewIndex)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual void undo() override { mpEditor->SetActiveTweakIndex(mOldIndex); }
|
||||||
|
virtual void redo() override { mpEditor->SetActiveTweakIndex(mNewIndex); }
|
||||||
|
virtual bool AffectsCleanState() const { return false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/** CTweakEditor functions */
|
||||||
|
CTweakEditor::CTweakEditor(QWidget* pParent)
|
||||||
|
: IEditor(pParent)
|
||||||
|
, mpUI(new Ui::CTweakEditor)
|
||||||
|
, mCurrentTweakIndex(-1)
|
||||||
|
, mHasBeenShown(false)
|
||||||
|
{
|
||||||
|
mpUI->setupUi(this);
|
||||||
|
mpUI->TweakTabs->setExpanding(false);
|
||||||
|
mpUI->ToolBar->addSeparator();
|
||||||
|
AddUndoActions(mpUI->ToolBar);
|
||||||
|
SET_WINDOWTITLE_APPVARS("%APP_FULL_NAME% - Tweak Editor[*]");
|
||||||
|
|
||||||
|
connect(mpUI->TweakTabs, SIGNAL(currentChanged(int)), this, SLOT(OnTweakTabClicked(int)));
|
||||||
|
connect(mpUI->ActionSave, SIGNAL(triggered(bool)), this, SLOT(Save()));
|
||||||
|
connect(mpUI->ActionSaveAndRepack, SIGNAL(triggered(bool)), this, SLOT(SaveAndRepack()));
|
||||||
|
}
|
||||||
|
|
||||||
|
CTweakEditor::~CTweakEditor()
|
||||||
|
{
|
||||||
|
delete mpUI;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CTweakEditor::HasTweaks()
|
||||||
|
{
|
||||||
|
return !mTweakAssets.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CTweakEditor::Save()
|
||||||
|
{
|
||||||
|
if (!gpEdApp->ActiveProject()->TweakManager()->SaveTweaks())
|
||||||
|
{
|
||||||
|
UICommon::ErrorMsg(this, "Tweaks failed to save!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UndoStack().setClean();
|
||||||
|
setWindowModified(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTweakEditor::SetActiveTweakData(CTweakData* pTweakData)
|
||||||
|
{
|
||||||
|
for( int TweakIdx = 0; TweakIdx < mTweakAssets.size(); TweakIdx++ )
|
||||||
|
{
|
||||||
|
if (mTweakAssets[TweakIdx] == pTweakData)
|
||||||
|
{
|
||||||
|
CSetTweakIndexCommand* pCommand = new CSetTweakIndexCommand(this, mCurrentTweakIndex, TweakIdx);
|
||||||
|
UndoStack().push(pCommand);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTweakEditor::SetActiveTweakIndex(int Index)
|
||||||
|
{
|
||||||
|
if( mCurrentTweakIndex != Index )
|
||||||
|
{
|
||||||
|
mCurrentTweakIndex = Index;
|
||||||
|
|
||||||
|
CTweakData* pTweakData = mTweakAssets[Index];
|
||||||
|
mpUI->PropertyView->SetIntrinsicProperties(pTweakData->TweakData());
|
||||||
|
|
||||||
|
mpUI->TweakTabs->blockSignals(true);
|
||||||
|
mpUI->TweakTabs->setCurrentIndex(Index);
|
||||||
|
mpUI->TweakTabs->blockSignals(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTweakEditor::OnTweakTabClicked(int Index)
|
||||||
|
{
|
||||||
|
if (Index != mCurrentTweakIndex)
|
||||||
|
{
|
||||||
|
CSetTweakIndexCommand* pCommand = new CSetTweakIndexCommand(this, mCurrentTweakIndex, Index);
|
||||||
|
UndoStack().push(pCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTweakEditor::OnProjectChanged(CGameProject* pNewProject)
|
||||||
|
{
|
||||||
|
// Close and clear tabs
|
||||||
|
mCurrentTweakIndex = -1;
|
||||||
|
mpUI->PropertyView->ClearProperties();
|
||||||
|
close();
|
||||||
|
|
||||||
|
mpUI->TweakTabs->blockSignals(true);
|
||||||
|
|
||||||
|
while (mpUI->TweakTabs->count() > 0)
|
||||||
|
{
|
||||||
|
mpUI->TweakTabs->removeTab(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
mTweakAssets.clear();
|
||||||
|
UndoStack().clear();
|
||||||
|
|
||||||
|
// Create tweak list
|
||||||
|
if (pNewProject != nullptr)
|
||||||
|
{
|
||||||
|
for (CTweakData* pTweakData : pNewProject->TweakManager()->TweakObjects())
|
||||||
|
{
|
||||||
|
mTweakAssets << pTweakData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort in alphabetical order and create tabs
|
||||||
|
if (!mTweakAssets.isEmpty())
|
||||||
|
{
|
||||||
|
qSort(mTweakAssets.begin(), mTweakAssets.end(), [](CTweakData* pLeft, CTweakData* pRight) -> bool {
|
||||||
|
return pLeft->TweakName().ToUpper() < pRight->TweakName().ToUpper();
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (CTweakData* pTweakData, mTweakAssets)
|
||||||
|
{
|
||||||
|
QString TweakName = TO_QSTRING( pTweakData->TweakName() );
|
||||||
|
mpUI->TweakTabs->addTab(TweakName);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetActiveTweakIndex(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
mpUI->TweakTabs->blockSignals(false);
|
||||||
|
|
||||||
|
// Hide "save and repack" button for MP2+ as it doesn't do anything different from the regular Save button
|
||||||
|
mpUI->ActionSaveAndRepack->setVisible( !pNewProject || pNewProject->Game() <= EGame::Prime );
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
#ifndef CTWEAKEDITOR_H
|
||||||
|
#define CTWEAKEDITOR_H
|
||||||
|
|
||||||
|
#include "Editor/IEditor.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class CTweakEditor;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CTweakEditor : public IEditor
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
/** Qt UI */
|
||||||
|
Ui::CTweakEditor* mpUI;
|
||||||
|
|
||||||
|
/** List of editable tweak assets */
|
||||||
|
QVector<CTweakData*> mTweakAssets;
|
||||||
|
|
||||||
|
/** Whether the editor window has been shown before */
|
||||||
|
bool mHasBeenShown;
|
||||||
|
|
||||||
|
/** Index of tweak data currently being edited */
|
||||||
|
int mCurrentTweakIndex;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CTweakEditor(QWidget* pParent = 0);
|
||||||
|
~CTweakEditor();
|
||||||
|
bool HasTweaks();
|
||||||
|
|
||||||
|
virtual bool Save() override;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void SetActiveTweakData(CTweakData* pTweakData);
|
||||||
|
void SetActiveTweakIndex(int Index);
|
||||||
|
void OnTweakTabClicked(int Index);
|
||||||
|
void OnProjectChanged(CGameProject* pNewProject);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CTWEAKEDITOR_H
|
|
@ -0,0 +1,120 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>CTweakEditor</class>
|
||||||
|
<widget class="QMainWindow" name="CTweakEditor">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>452</width>
|
||||||
|
<height>644</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Tweaks Editor</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralwidget">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QTabBar" name="TweakTabs" native="true"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="CPropertyView" name="PropertyView">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>10</pointsize>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="alternatingRowColors">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="selectionMode">
|
||||||
|
<enum>QAbstractItemView::NoSelection</enum>
|
||||||
|
</property>
|
||||||
|
<property name="verticalScrollMode">
|
||||||
|
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QToolBar" name="ToolBar">
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>toolBar</string>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<attribute name="toolBarArea">
|
||||||
|
<enum>TopToolBarArea</enum>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="toolBarBreak">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
|
<addaction name="ActionSave"/>
|
||||||
|
<addaction name="ActionSaveAndRepack"/>
|
||||||
|
</widget>
|
||||||
|
<action name="ActionSave">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="Icons.qrc">
|
||||||
|
<normaloff>:/icons/Save.png</normaloff>:/icons/Save.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Save</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Save</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+S</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="ActionSaveAndRepack">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="Icons.qrc">
|
||||||
|
<normaloff>:/icons/SaveAndRepack_32px.png</normaloff>:/icons/SaveAndRepack_32px.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Save and Cook</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Save and Cook</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>QTabBar</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header location="global">QTabBar</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>CPropertyView</class>
|
||||||
|
<extends>QTreeView</extends>
|
||||||
|
<header>Editor/PropertyEdit/CPropertyView.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<resources>
|
||||||
|
<include location="Icons.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -25,9 +25,16 @@ public:
|
||||||
|
|
||||||
// Note: All function calls should be deferred with QMetaObject::invokeMethod to ensure
|
// Note: All function calls should be deferred with QMetaObject::invokeMethod to ensure
|
||||||
// that they run on the UI thread instead of whatever thread we happen to be on.
|
// that they run on the UI thread instead of whatever thread we happen to be on.
|
||||||
virtual void AsyncMessageBox(const TString& rkInfoBoxTitle, const TString& rkMessage)
|
virtual void ShowMessageBox(const TString& rkInfoBoxTitle, const TString& rkMessage)
|
||||||
{
|
{
|
||||||
QMetaObject::invokeMethod(this, "AsyncMessageBoxSlot", Qt::QueuedConnection,
|
QMetaObject::invokeMethod(this, "MessageBoxSlot", GetConnectionType(),
|
||||||
|
Q_ARG(QString, TO_QSTRING(rkInfoBoxTitle)),
|
||||||
|
Q_ARG(QString, TO_QSTRING(rkMessage)) );
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void ShowMessageBoxAsync(const TString& rkInfoBoxTitle, const TString& rkMessage)
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(this, "MessageBoxSlot", Qt::QueuedConnection,
|
||||||
Q_ARG(QString, TO_QSTRING(rkInfoBoxTitle)),
|
Q_ARG(QString, TO_QSTRING(rkInfoBoxTitle)),
|
||||||
Q_ARG(QString, TO_QSTRING(rkMessage)) );
|
Q_ARG(QString, TO_QSTRING(rkMessage)) );
|
||||||
}
|
}
|
||||||
|
@ -42,8 +49,17 @@ public:
|
||||||
return RetVal;
|
return RetVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public slots:
|
virtual bool OpenProject(const TString& kPath = "")
|
||||||
void AsyncMessageBoxSlot(const QString& rkInfoBoxTitle, const QString& rkMessage)
|
{
|
||||||
|
bool RetVal;
|
||||||
|
QMetaObject::invokeMethod(this, "OpenProjectSlot", GetConnectionType(),
|
||||||
|
Q_RETURN_ARG(bool, RetVal),
|
||||||
|
Q_ARG(QString, TO_QSTRING(kPath)) );
|
||||||
|
return RetVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void MessageBoxSlot(const QString& rkInfoBoxTitle, const QString& rkMessage)
|
||||||
{
|
{
|
||||||
UICommon::InfoMsg(gpEdApp->WorldEditor(), rkInfoBoxTitle, rkMessage);
|
UICommon::InfoMsg(gpEdApp->WorldEditor(), rkInfoBoxTitle, rkMessage);
|
||||||
}
|
}
|
||||||
|
@ -52,6 +68,11 @@ public slots:
|
||||||
{
|
{
|
||||||
return UICommon::YesNoQuestion(gpEdApp->WorldEditor(), rkInfoBoxTitle, rkQuestion);
|
return UICommon::YesNoQuestion(gpEdApp->WorldEditor(), rkInfoBoxTitle, rkQuestion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool OpenProjectSlot(const QString& kPath)
|
||||||
|
{
|
||||||
|
return !kPath.isEmpty() ? gpEdApp->OpenProject(kPath) : UICommon::OpenProject();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CUIRELAY_H
|
#endif // CUIRELAY_H
|
||||||
|
|
|
@ -96,14 +96,9 @@ HEADERS += \
|
||||||
Undo/CTranslateNodeCommand.h \
|
Undo/CTranslateNodeCommand.h \
|
||||||
Undo/EUndoCommand.h \
|
Undo/EUndoCommand.h \
|
||||||
Undo/UndoCommands.h \
|
Undo/UndoCommands.h \
|
||||||
Widgets/IPreviewPanel.h \
|
|
||||||
Widgets/WColorPicker.h \
|
Widgets/WColorPicker.h \
|
||||||
Widgets/WDraggableSpinBox.h \
|
Widgets/WDraggableSpinBox.h \
|
||||||
Widgets/WIntegralSpinBox.h \
|
Widgets/WIntegralSpinBox.h \
|
||||||
Widgets/WScanPreviewPanel.h \
|
|
||||||
Widgets/WStringPreviewPanel.h \
|
|
||||||
Widgets/WTextureGLWidget.h \
|
|
||||||
Widgets/WTexturePreviewPanel.h \
|
|
||||||
Widgets/WVectorEditor.h \
|
Widgets/WVectorEditor.h \
|
||||||
WorldEditor/CLayerEditor.h \
|
WorldEditor/CLayerEditor.h \
|
||||||
WorldEditor/CLayerModel.h \
|
WorldEditor/CLayerModel.h \
|
||||||
|
@ -198,7 +193,18 @@ HEADERS += \
|
||||||
Widgets/CCheckableTreeWidgetItem.h \
|
Widgets/CCheckableTreeWidgetItem.h \
|
||||||
Widgets/CCheckableTreeWidget.h \
|
Widgets/CCheckableTreeWidget.h \
|
||||||
Undo/IEditPropertyCommand.h \
|
Undo/IEditPropertyCommand.h \
|
||||||
Widgets/TEnumComboBox.h
|
Widgets/TEnumComboBox.h \
|
||||||
|
StringEditor/CStringEditor.h \
|
||||||
|
StringEditor/CStringListModel.h \
|
||||||
|
StringEditor/CStringDelegate.h \
|
||||||
|
CCustomDelegate.h \
|
||||||
|
CTweakEditor.h \
|
||||||
|
Undo/CEditIntrinsicPropertyCommand.h \
|
||||||
|
Undo/TSerializeUndoCommand.h \
|
||||||
|
StringEditor/CStringMimeData.h \
|
||||||
|
ScanEditor/CScanEditor.h \
|
||||||
|
Undo/ICreateDeleteResourceCommand.h \
|
||||||
|
Undo/CSaveStoreCommand.h
|
||||||
|
|
||||||
# Source Files
|
# Source Files
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
@ -207,14 +213,9 @@ SOURCES += \
|
||||||
Undo/CRotateNodeCommand.cpp \
|
Undo/CRotateNodeCommand.cpp \
|
||||||
Undo/CScaleNodeCommand.cpp \
|
Undo/CScaleNodeCommand.cpp \
|
||||||
Undo/CTranslateNodeCommand.cpp \
|
Undo/CTranslateNodeCommand.cpp \
|
||||||
Widgets/IPreviewPanel.cpp \
|
|
||||||
Widgets/WColorPicker.cpp \
|
Widgets/WColorPicker.cpp \
|
||||||
Widgets/WDraggableSpinBox.cpp \
|
Widgets/WDraggableSpinBox.cpp \
|
||||||
Widgets/WIntegralSpinBox.cpp \
|
Widgets/WIntegralSpinBox.cpp \
|
||||||
Widgets/WScanPreviewPanel.cpp \
|
|
||||||
Widgets/WStringPreviewPanel.cpp \
|
|
||||||
Widgets/WTextureGLWidget.cpp \
|
|
||||||
Widgets/WTexturePreviewPanel.cpp \
|
|
||||||
Widgets/WVectorEditor.cpp \
|
Widgets/WVectorEditor.cpp \
|
||||||
WorldEditor/CLayerEditor.cpp \
|
WorldEditor/CLayerEditor.cpp \
|
||||||
WorldEditor/CLayerModel.cpp \
|
WorldEditor/CLayerModel.cpp \
|
||||||
|
@ -273,14 +274,18 @@ SOURCES += \
|
||||||
ResourceBrowser/CVirtualDirectoryTreeView.cpp \
|
ResourceBrowser/CVirtualDirectoryTreeView.cpp \
|
||||||
CPropertyNameValidator.cpp \
|
CPropertyNameValidator.cpp \
|
||||||
CGeneratePropertyNamesDialog.cpp \
|
CGeneratePropertyNamesDialog.cpp \
|
||||||
Undo/IEditPropertyCommand.cpp
|
Undo/IEditPropertyCommand.cpp \
|
||||||
|
StringEditor/CStringEditor.cpp \
|
||||||
|
StringEditor/CStringListModel.cpp \
|
||||||
|
IEditor.cpp \
|
||||||
|
StringEditor/CStringDelegate.cpp \
|
||||||
|
CTweakEditor.cpp \
|
||||||
|
ScanEditor/CScanEditor.cpp
|
||||||
|
|
||||||
# UI Files
|
# UI Files
|
||||||
FORMS += \
|
FORMS += \
|
||||||
TestDialog.ui \
|
TestDialog.ui \
|
||||||
ModelEditor/CModelEditorWindow.ui \
|
ModelEditor/CModelEditorWindow.ui \
|
||||||
Widgets/WScanPreviewPanel.ui \
|
|
||||||
Widgets/WTexturePreviewPanel.ui \
|
|
||||||
WorldEditor/CLayerEditor.ui \
|
WorldEditor/CLayerEditor.ui \
|
||||||
WorldEditor/CWorldEditor.ui \
|
WorldEditor/CWorldEditor.ui \
|
||||||
WorldEditor/WCreateTab.ui \
|
WorldEditor/WCreateTab.ui \
|
||||||
|
@ -300,7 +305,10 @@ FORMS += \
|
||||||
WorldEditor/CPoiMapSidebar.ui \
|
WorldEditor/CPoiMapSidebar.ui \
|
||||||
CProgressDialog.ui \
|
CProgressDialog.ui \
|
||||||
Widgets/CSelectResourcePanel.ui \
|
Widgets/CSelectResourcePanel.ui \
|
||||||
CGeneratePropertyNamesDialog.ui
|
CGeneratePropertyNamesDialog.ui \
|
||||||
|
StringEditor/CStringEditor.ui \
|
||||||
|
CTweakEditor.ui \
|
||||||
|
ScanEditor/CScanEditor.ui
|
||||||
|
|
||||||
# Codegen
|
# Codegen
|
||||||
CODEGEN_DIR = $$EXTERNALS_DIR/CodeGen
|
CODEGEN_DIR = $$EXTERNALS_DIR/CodeGen
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
#include "IEditor.h"
|
||||||
|
|
||||||
|
#include "Editor/Undo/IUndoCommand.h"
|
||||||
|
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QToolBar>
|
||||||
|
|
||||||
|
IEditor::IEditor(QWidget* pParent)
|
||||||
|
: QMainWindow(pParent)
|
||||||
|
{
|
||||||
|
// Register the editor window
|
||||||
|
gpEdApp->AddEditor(this);
|
||||||
|
|
||||||
|
// Create undo actions
|
||||||
|
QAction *pUndoAction = mUndoStack.createUndoAction(this);
|
||||||
|
QAction *pRedoAction = mUndoStack.createRedoAction(this);
|
||||||
|
pUndoAction->setShortcut(QKeySequence::Undo);
|
||||||
|
pRedoAction->setShortcut(QKeySequence::Redo);
|
||||||
|
pUndoAction->setIcon(QIcon(":/icons/Undo.png"));
|
||||||
|
pRedoAction->setIcon(QIcon(":/icons/Redo.png"));
|
||||||
|
mUndoActions.push_back(pUndoAction);
|
||||||
|
mUndoActions.push_back(pRedoAction);
|
||||||
|
|
||||||
|
connect(&mUndoStack, SIGNAL(indexChanged(int)), this, SLOT(OnUndoStackIndexChanged()));
|
||||||
|
}
|
||||||
|
|
||||||
|
QUndoStack& IEditor::UndoStack()
|
||||||
|
{
|
||||||
|
return mUndoStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IEditor::AddUndoActions(QToolBar* pToolBar, QAction* pBefore /*= 0*/)
|
||||||
|
{
|
||||||
|
pToolBar->insertActions(pBefore, mUndoActions);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IEditor::AddUndoActions(QMenu* pMenu, QAction* pBefore /*= 0*/)
|
||||||
|
{
|
||||||
|
pMenu->insertActions(pBefore, mUndoActions);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IEditor::CheckUnsavedChanges()
|
||||||
|
{
|
||||||
|
// Check whether the user has unsaved changes, return whether it's okay to clear the scene
|
||||||
|
bool OkToClear = !isWindowModified();
|
||||||
|
|
||||||
|
if (!OkToClear)
|
||||||
|
{
|
||||||
|
int Result = QMessageBox::warning(this, "Save", "You have unsaved changes. Save?", QMessageBox::Yes, QMessageBox::No, QMessageBox::Cancel);
|
||||||
|
|
||||||
|
if (Result == QMessageBox::Yes)
|
||||||
|
OkToClear = Save();
|
||||||
|
|
||||||
|
else if (Result == QMessageBox::No)
|
||||||
|
{
|
||||||
|
mUndoStack.setIndex(0); // Revert all changes
|
||||||
|
OkToClear = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (Result == QMessageBox::Cancel)
|
||||||
|
OkToClear = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OkToClear;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** QMainWindow overrides */
|
||||||
|
void IEditor::closeEvent(QCloseEvent* pEvent)
|
||||||
|
{
|
||||||
|
if (CheckUnsavedChanges())
|
||||||
|
{
|
||||||
|
mUndoStack.clear();
|
||||||
|
pEvent->accept();
|
||||||
|
emit Closed();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pEvent->ignore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Non-virtual slots */
|
||||||
|
bool IEditor::SaveAndRepack()
|
||||||
|
{
|
||||||
|
if (Save())
|
||||||
|
{
|
||||||
|
gpEdApp->CookAllDirtyPackages();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void IEditor::OnUndoStackIndexChanged()
|
||||||
|
{
|
||||||
|
// Check the commands that have been executed on the undo stack and find out whether any of them affect the clean state.
|
||||||
|
// This is to prevent commands like select/deselect from altering the clean state.
|
||||||
|
int CurrentIndex = mUndoStack.index();
|
||||||
|
int CleanIndex = mUndoStack.cleanIndex();
|
||||||
|
|
||||||
|
if (CleanIndex == -1)
|
||||||
|
{
|
||||||
|
if (!isWindowModified())
|
||||||
|
mUndoStack.setClean();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CurrentIndex == CleanIndex)
|
||||||
|
setWindowModified(false);
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool IsClean = true;
|
||||||
|
int LowIndex = (CurrentIndex > CleanIndex ? CleanIndex : CurrentIndex);
|
||||||
|
int HighIndex = (CurrentIndex > CleanIndex ? CurrentIndex - 1 : CleanIndex - 1);
|
||||||
|
|
||||||
|
for (int i = LowIndex; i <= HighIndex; i++)
|
||||||
|
{
|
||||||
|
const QUndoCommand *pkQCmd = mUndoStack.command(i);
|
||||||
|
|
||||||
|
if (const IUndoCommand* pkCmd = dynamic_cast<const IUndoCommand*>(pkQCmd))
|
||||||
|
{
|
||||||
|
if (pkCmd->AffectsCleanState())
|
||||||
|
IsClean = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (pkQCmd->childCount() > 0)
|
||||||
|
{
|
||||||
|
for (int ChildIdx = 0; ChildIdx < pkQCmd->childCount(); ChildIdx++)
|
||||||
|
{
|
||||||
|
const IUndoCommand *pkCmd = static_cast<const IUndoCommand*>(pkQCmd->child(ChildIdx));
|
||||||
|
|
||||||
|
if (pkCmd->AffectsCleanState())
|
||||||
|
{
|
||||||
|
IsClean = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsClean) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
setWindowModified(!IsClean);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,23 +2,50 @@
|
||||||
#define IEDITOR
|
#define IEDITOR
|
||||||
|
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
|
#include <QAction>
|
||||||
|
#include <QList>
|
||||||
|
#include <QUndoStack>
|
||||||
|
|
||||||
#include "CEditorApplication.h"
|
#include "CEditorApplication.h"
|
||||||
|
|
||||||
|
/** Base class of all editor windows */
|
||||||
class IEditor : public QMainWindow
|
class IEditor : public QMainWindow
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
protected:
|
||||||
IEditor(QWidget *pParent)
|
// Undo stack
|
||||||
: QMainWindow(pParent)
|
QUndoStack mUndoStack;
|
||||||
{
|
QList<QAction*> mUndoActions;
|
||||||
gpEdApp->AddEditor(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void closeEvent(QCloseEvent*) { emit Closed(); }
|
public:
|
||||||
|
IEditor(QWidget* pParent);
|
||||||
|
QUndoStack& UndoStack();
|
||||||
|
void AddUndoActions(QToolBar* pToolBar, QAction* pBefore = 0);
|
||||||
|
void AddUndoActions(QMenu* pMenu, QAction* pBefore = 0);
|
||||||
|
bool CheckUnsavedChanges();
|
||||||
|
|
||||||
|
/** QMainWindow overrides */
|
||||||
|
virtual void closeEvent(QCloseEvent*);
|
||||||
|
|
||||||
|
/** Interface */
|
||||||
virtual void EditorTick(float /*DeltaTime*/) { }
|
virtual void EditorTick(float /*DeltaTime*/) { }
|
||||||
virtual CBasicViewport* Viewport() const { return nullptr; }
|
virtual CBasicViewport* Viewport() const { return nullptr; }
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
/** Virtual slots */
|
||||||
|
virtual bool Save()
|
||||||
|
{
|
||||||
|
// Default implementation for editor windows that do not support resaving assets.
|
||||||
|
// This should not be called.
|
||||||
|
errorf("Base IEditor::Save() implementation called. Changes will not be saved.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Non-virtual slots */
|
||||||
|
bool SaveAndRepack();
|
||||||
|
void OnUndoStackIndexChanged();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void Closed();
|
void Closed();
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,14 +15,6 @@ INodeEditor::INodeEditor(QWidget *pParent)
|
||||||
, mRotateSpace(ETransformSpace::World)
|
, mRotateSpace(ETransformSpace::World)
|
||||||
, mCloneState(eNotCloning)
|
, mCloneState(eNotCloning)
|
||||||
{
|
{
|
||||||
// Create undo actions
|
|
||||||
QAction *pUndoAction = mUndoStack.createUndoAction(this);
|
|
||||||
QAction *pRedoAction = mUndoStack.createRedoAction(this);
|
|
||||||
pUndoAction->setShortcut(QKeySequence::Undo);
|
|
||||||
pRedoAction->setShortcut(QKeySequence::Redo);
|
|
||||||
mUndoActions.push_back(pUndoAction);
|
|
||||||
mUndoActions.push_back(pRedoAction);
|
|
||||||
|
|
||||||
// Create gizmo actions
|
// Create gizmo actions
|
||||||
mGizmoActions.append(new QAction(QIcon(":/icons/SelectMode.png"), "Select Objects", this));
|
mGizmoActions.append(new QAction(QIcon(":/icons/SelectMode.png"), "Select Objects", this));
|
||||||
mGizmoActions.append(new QAction(QIcon(":/icons/Translate.png"), "Translate", this));
|
mGizmoActions.append(new QAction(QIcon(":/icons/Translate.png"), "Translate", this));
|
||||||
|
@ -63,11 +55,6 @@ INodeEditor::~INodeEditor()
|
||||||
delete mpSelection;
|
delete mpSelection;
|
||||||
}
|
}
|
||||||
|
|
||||||
QUndoStack* INodeEditor::UndoStack()
|
|
||||||
{
|
|
||||||
return &mUndoStack;
|
|
||||||
}
|
|
||||||
|
|
||||||
CScene* INodeEditor::Scene()
|
CScene* INodeEditor::Scene()
|
||||||
{
|
{
|
||||||
return &mScene;
|
return &mScene;
|
||||||
|
|
|
@ -12,17 +12,12 @@
|
||||||
#include <QActionGroup>
|
#include <QActionGroup>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QUndoStack>
|
|
||||||
|
|
||||||
class INodeEditor : public IEditor
|
class INodeEditor : public IEditor
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Undo stack
|
|
||||||
QUndoStack mUndoStack;
|
|
||||||
QList<QAction*> mUndoActions;
|
|
||||||
|
|
||||||
// Node management
|
// Node management
|
||||||
CScene mScene;
|
CScene mScene;
|
||||||
CNodeSelection *mpSelection;
|
CNodeSelection *mpSelection;
|
||||||
|
@ -55,7 +50,6 @@ protected:
|
||||||
public:
|
public:
|
||||||
explicit INodeEditor(QWidget *pParent = 0);
|
explicit INodeEditor(QWidget *pParent = 0);
|
||||||
virtual ~INodeEditor();
|
virtual ~INodeEditor();
|
||||||
QUndoStack* UndoStack();
|
|
||||||
CScene* Scene();
|
CScene* Scene();
|
||||||
CGizmo* Gizmo();
|
CGizmo* Gizmo();
|
||||||
bool IsGizmoVisible();
|
bool IsGizmoVisible();
|
||||||
|
|
|
@ -151,6 +151,17 @@ CModelEditorWindow::~CModelEditorWindow()
|
||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CModelEditorWindow::Save()
|
||||||
|
{
|
||||||
|
if (!mpCurrentModel) return true;
|
||||||
|
bool SaveSuccess = mpCurrentModel->Entry()->Save();
|
||||||
|
|
||||||
|
if (SaveSuccess)
|
||||||
|
gpEdApp->NotifyAssetsModified();
|
||||||
|
|
||||||
|
return SaveSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
void CModelEditorWindow::RefreshViewport()
|
void CModelEditorWindow::RefreshViewport()
|
||||||
{
|
{
|
||||||
ui->Viewport->ProcessInput();
|
ui->Viewport->ProcessInput();
|
||||||
|
@ -749,15 +760,6 @@ void CModelEditorWindow::Import()
|
||||||
gpResourceStore->DestroyUnreferencedResources();
|
gpResourceStore->DestroyUnreferencedResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CModelEditorWindow::Save()
|
|
||||||
{
|
|
||||||
if (!mpCurrentModel) return;
|
|
||||||
bool SaveSuccess = mpCurrentModel->Entry()->Save();
|
|
||||||
|
|
||||||
if (SaveSuccess)
|
|
||||||
gpEdApp->NotifyAssetsModified();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CModelEditorWindow::ConvertToDDS()
|
void CModelEditorWindow::ConvertToDDS()
|
||||||
{
|
{
|
||||||
QString Input = QFileDialog::getOpenFileName(this, "Retro Texture (*.TXTR)", "", "*.TXTR");
|
QString Input = QFileDialog::getOpenFileName(this, "Retro Texture (*.TXTR)", "", "*.TXTR");
|
||||||
|
|
|
@ -34,6 +34,7 @@ class CModelEditorWindow : public IEditor
|
||||||
public:
|
public:
|
||||||
explicit CModelEditorWindow(CModel *pModel, QWidget *pParent = 0);
|
explicit CModelEditorWindow(CModel *pModel, QWidget *pParent = 0);
|
||||||
~CModelEditorWindow();
|
~CModelEditorWindow();
|
||||||
|
bool Save();
|
||||||
void SetActiveModel(CModel *pModel);
|
void SetActiveModel(CModel *pModel);
|
||||||
CModelEditorViewport* Viewport() const;
|
CModelEditorViewport* Viewport() const;
|
||||||
|
|
||||||
|
@ -100,7 +101,6 @@ private:
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void Import();
|
void Import();
|
||||||
void Save();
|
|
||||||
void ConvertToDDS();
|
void ConvertToDDS();
|
||||||
void ConvertToTXTR();
|
void ConvertToTXTR();
|
||||||
void SetMeshPreview();
|
void SetMeshPreview();
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "Editor/UICommon.h"
|
#include "Editor/UICommon.h"
|
||||||
#include "Editor/Undo/CEditScriptPropertyCommand.h"
|
#include "Editor/Undo/CEditScriptPropertyCommand.h"
|
||||||
|
#include "Editor/Undo/CEditIntrinsicPropertyCommand.h"
|
||||||
#include "Editor/Undo/CResizeScriptArrayCommand.h"
|
#include "Editor/Undo/CResizeScriptArrayCommand.h"
|
||||||
#include "Editor/Widgets/CResourceSelector.h"
|
#include "Editor/Widgets/CResourceSelector.h"
|
||||||
#include "Editor/Widgets/WColorPicker.h"
|
#include "Editor/Widgets/WColorPicker.h"
|
||||||
|
@ -25,7 +26,7 @@
|
||||||
connect(pRelay, SIGNAL(WidgetEdited(QWidget*, const QModelIndex&)), this, SLOT(WidgetEdited(QWidget*, const QModelIndex&))); \
|
connect(pRelay, SIGNAL(WidgetEdited(QWidget*, const QModelIndex&)), this, SLOT(WidgetEdited(QWidget*, const QModelIndex&))); \
|
||||||
}
|
}
|
||||||
|
|
||||||
CPropertyDelegate::CPropertyDelegate(QObject *pParent /*= 0*/)
|
CPropertyDelegate::CPropertyDelegate(QObject* pParent /*= 0*/)
|
||||||
: QStyledItemDelegate(pParent)
|
: QStyledItemDelegate(pParent)
|
||||||
, mpEditor(nullptr)
|
, mpEditor(nullptr)
|
||||||
, mpModel(nullptr)
|
, mpModel(nullptr)
|
||||||
|
@ -33,19 +34,20 @@ CPropertyDelegate::CPropertyDelegate(QObject *pParent /*= 0*/)
|
||||||
, mEditInProgress(false)
|
, mEditInProgress(false)
|
||||||
, mRelaysBlocked(false)
|
, mRelaysBlocked(false)
|
||||||
{
|
{
|
||||||
|
mpEditor = UICommon::FindAncestor<IEditor>(pParent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPropertyDelegate::SetPropertyModel(CPropertyModel *pModel)
|
void CPropertyDelegate::SetEditor(IEditor* pEditor)
|
||||||
{
|
|
||||||
mpModel = pModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CPropertyDelegate::SetEditor(CWorldEditor *pEditor)
|
|
||||||
{
|
{
|
||||||
mpEditor = pEditor;
|
mpEditor = pEditor;
|
||||||
}
|
}
|
||||||
|
|
||||||
QWidget* CPropertyDelegate::createEditor(QWidget *pParent, const QStyleOptionViewItem& /*rkOption*/, const QModelIndex& rkIndex) const
|
void CPropertyDelegate::SetPropertyModel(CPropertyModel* pModel)
|
||||||
|
{
|
||||||
|
mpModel = pModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget* CPropertyDelegate::createEditor(QWidget* pParent, const QStyleOptionViewItem& /*rkOption*/, const QModelIndex& rkIndex) const
|
||||||
{
|
{
|
||||||
if (!mpModel) return nullptr;
|
if (!mpModel) return nullptr;
|
||||||
IProperty *pProp = mpModel->PropertyForIndex(rkIndex, false);
|
IProperty *pProp = mpModel->PropertyForIndex(rkIndex, false);
|
||||||
|
@ -366,16 +368,28 @@ void CPropertyDelegate::setModelData(QWidget *pEditor, QAbstractItemModel* /*pMo
|
||||||
if (pProp)
|
if (pProp)
|
||||||
{
|
{
|
||||||
EPropertyType Type = mpModel->GetEffectiveFieldType(pProp);
|
EPropertyType Type = mpModel->GetEffectiveFieldType(pProp);
|
||||||
|
CScriptObject* pObject = mpModel->GetScriptObject();
|
||||||
|
|
||||||
|
if (!pObject)
|
||||||
|
{
|
||||||
|
QVector<void*> DataPointers;
|
||||||
|
DataPointers << pData;
|
||||||
|
pCommand = new CEditIntrinsicPropertyCommand(pProp, DataPointers, mpModel, rkIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
QVector<CScriptObject*> Objects;
|
QVector<CScriptObject*> Objects;
|
||||||
Objects << mpModel->GetScriptObject();
|
Objects << pObject;
|
||||||
|
|
||||||
|
pCommand = (Type != EPropertyType::Array) ?
|
||||||
|
new CEditScriptPropertyCommand(pProp, Objects, mpModel, rkIndex) :
|
||||||
|
new CResizeScriptArrayCommand (pProp, Objects, mpModel, rkIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
pCommand->SaveOldData();
|
||||||
|
|
||||||
if (Type != EPropertyType::Array)
|
if (Type != EPropertyType::Array)
|
||||||
{
|
{
|
||||||
// TODO: support this for non script object properties
|
|
||||||
pCommand = new CEditScriptPropertyCommand(pProp, mpEditor, Objects, rkIndex);
|
|
||||||
pCommand->SaveOldData();
|
|
||||||
|
|
||||||
// Handle sub-properties of flags and animation sets
|
// Handle sub-properties of flags and animation sets
|
||||||
if (rkIndex.internalId() & 0x80000000)
|
if (rkIndex.internalId() & 0x80000000)
|
||||||
{
|
{
|
||||||
|
@ -487,9 +501,6 @@ void CPropertyDelegate::setModelData(QWidget *pEditor, QAbstractItemModel* /*pMo
|
||||||
// Array
|
// Array
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pCommand = new CResizeScriptArrayCommand(pProp, mpEditor, Objects, mpModel, rkIndex);
|
|
||||||
pCommand->SaveOldData();
|
|
||||||
|
|
||||||
WIntegralSpinBox* pSpinBox = static_cast<WIntegralSpinBox*>(pEditor);
|
WIntegralSpinBox* pSpinBox = static_cast<WIntegralSpinBox*>(pEditor);
|
||||||
CArrayProperty* pArray = static_cast<CArrayProperty*>(pProp);
|
CArrayProperty* pArray = static_cast<CArrayProperty*>(pProp);
|
||||||
int OldCount = pArray->ArrayCount(pData);
|
int OldCount = pArray->ArrayCount(pData);
|
||||||
|
@ -525,7 +536,7 @@ void CPropertyDelegate::setModelData(QWidget *pEditor, QAbstractItemModel* /*pMo
|
||||||
{
|
{
|
||||||
// Always consider the edit done for bool properties
|
// Always consider the edit done for bool properties
|
||||||
pCommand->SetEditComplete(!mEditInProgress || pProp->Type() == EPropertyType::Bool);
|
pCommand->SetEditComplete(!mEditInProgress || pProp->Type() == EPropertyType::Bool);
|
||||||
mpEditor->UndoStack()->push(pCommand);
|
mpEditor->UndoStack().push(pCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
|
@ -565,9 +576,9 @@ QWidget* CPropertyDelegate::CreateCharacterEditor(QWidget *pParent, const QModel
|
||||||
pSelector->SetFrameVisible(false);
|
pSelector->SetFrameVisible(false);
|
||||||
|
|
||||||
if (Params.Version() <= EGame::Echoes)
|
if (Params.Version() <= EGame::Echoes)
|
||||||
pSelector->SetTypeFilter(mpEditor->CurrentGame(), "ANCS");
|
pSelector->SetTypeFilter(gpEdApp->CurrentGame(), "ANCS");
|
||||||
else
|
else
|
||||||
pSelector->SetTypeFilter(mpEditor->CurrentGame(), "CHAR");
|
pSelector->SetTypeFilter(gpEdApp->CurrentGame(), "CHAR");
|
||||||
|
|
||||||
CONNECT_RELAY(pSelector, rkIndex, ResourceChanged(CResourceEntry*));
|
CONNECT_RELAY(pSelector, rkIndex, ResourceChanged(CResourceEntry*));
|
||||||
return pSelector;
|
return pSelector;
|
||||||
|
@ -631,7 +642,7 @@ void CPropertyDelegate::SetCharacterModelData(QWidget *pEditor, const QModelInde
|
||||||
if (Type == EPropertyType::Asset)
|
if (Type == EPropertyType::Asset)
|
||||||
{
|
{
|
||||||
CResourceEntry *pEntry = static_cast<CResourceSelector*>(pEditor)->Entry();
|
CResourceEntry *pEntry = static_cast<CResourceSelector*>(pEditor)->Entry();
|
||||||
Params.SetResource( pEntry ? pEntry->ID() : CAssetID::InvalidID(mpEditor->CurrentGame()) );
|
Params.SetResource( pEntry ? pEntry->ID() : CAssetID::InvalidID(gpEdApp->CurrentGame()) );
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (Type == EPropertyType::Enum || Type == EPropertyType::Choice)
|
else if (Type == EPropertyType::Enum || Type == EPropertyType::Choice)
|
||||||
|
|
|
@ -9,29 +9,29 @@ class CPropertyDelegate : public QStyledItemDelegate
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
CWorldEditor *mpEditor;
|
IEditor* mpEditor;
|
||||||
CPropertyModel *mpModel;
|
CPropertyModel* mpModel;
|
||||||
bool mInRelayWidgetEdit;
|
bool mInRelayWidgetEdit;
|
||||||
mutable bool mEditInProgress;
|
mutable bool mEditInProgress;
|
||||||
mutable bool mRelaysBlocked;
|
mutable bool mRelaysBlocked;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CPropertyDelegate(QObject *pParent = 0);
|
CPropertyDelegate(QObject* pParent = 0);
|
||||||
void SetPropertyModel(CPropertyModel *pModel);
|
void SetEditor(IEditor* pEditor);
|
||||||
void SetEditor(CWorldEditor *pEditor);
|
void SetPropertyModel(CPropertyModel* pModel);
|
||||||
|
|
||||||
virtual QWidget* createEditor(QWidget *pParent, const QStyleOptionViewItem& rkOption, const QModelIndex& rkIndex) const;
|
virtual QWidget* createEditor(QWidget* pParent, const QStyleOptionViewItem& rkOption, const QModelIndex& rkIndex) const;
|
||||||
virtual void setEditorData(QWidget *pEditor, const QModelIndex &rkIndex) const;
|
virtual void setEditorData(QWidget* pEditor, const QModelIndex& rkIndex) const;
|
||||||
virtual void setModelData(QWidget *pEditor, QAbstractItemModel *pModel, const QModelIndex &rkIndex) const;
|
virtual void setModelData(QWidget* pEditor, QAbstractItemModel* pModel, const QModelIndex& rkIndex) const;
|
||||||
bool eventFilter(QObject *pObject, QEvent *pEvent);
|
bool eventFilter(QObject* pObject, QEvent* pEvent);
|
||||||
|
|
||||||
QWidget* CreateCharacterEditor(QWidget *pParent, const QModelIndex& rkIndex) const;
|
QWidget* CreateCharacterEditor(QWidget* pParent, const QModelIndex& rkIndex) const;
|
||||||
void SetCharacterEditorData(QWidget *pEditor, const QModelIndex& rkIndex) const;
|
void SetCharacterEditorData(QWidget* pEditor, const QModelIndex& rkIndex) const;
|
||||||
void SetCharacterModelData(QWidget *pEditor, const QModelIndex& rkIndex) const;
|
void SetCharacterModelData(QWidget* pEditor, const QModelIndex& rkIndex) const;
|
||||||
EPropertyType DetermineCharacterPropType(EGame Game, const QModelIndex& rkIndex) const;
|
EPropertyType DetermineCharacterPropType(EGame Game, const QModelIndex& rkIndex) const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void WidgetEdited(QWidget *pWidget, const QModelIndex& rkIndex);
|
void WidgetEdited(QWidget* pWidget, const QModelIndex& rkIndex);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void BlockRelays(bool Block) const { mRelaysBlocked = Block; }
|
void BlockRelays(bool Block) const { mRelaysBlocked = Block; }
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
|
|
||||||
CPropertyView::CPropertyView(QWidget *pParent)
|
CPropertyView::CPropertyView(QWidget *pParent)
|
||||||
: QTreeView(pParent)
|
: QTreeView(pParent)
|
||||||
, mpEditor(nullptr)
|
|
||||||
, mpMenuProperty(nullptr)
|
, mpMenuProperty(nullptr)
|
||||||
{
|
{
|
||||||
mpModel = new CPropertyModel(this);
|
mpModel = new CPropertyModel(this);
|
||||||
|
@ -80,21 +79,36 @@ bool CPropertyView::event(QEvent *pEvent)
|
||||||
pEvent->ignore();
|
pEvent->ignore();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
else if (pEvent->type() == QEvent::Resize && !isVisible())
|
||||||
|
{
|
||||||
|
resizeColumnToContents(0);
|
||||||
|
}
|
||||||
|
|
||||||
else return QTreeView::event(pEvent);
|
return QTreeView::event(pEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPropertyView::SetEditor(CWorldEditor *pEditor)
|
int CPropertyView::sizeHintForColumn(int Column) const
|
||||||
|
{
|
||||||
|
if (Column == 0)
|
||||||
|
return width() * 0.6f;
|
||||||
|
else
|
||||||
|
return width() * 0.4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPropertyView::SetEditor(IEditor* pEditor)
|
||||||
{
|
{
|
||||||
mpEditor = pEditor;
|
|
||||||
mpDelegate->SetEditor(pEditor);
|
mpDelegate->SetEditor(pEditor);
|
||||||
connect(mpEditor, SIGNAL(PropertyModified(CScriptObject*,IProperty*)), mpModel, SLOT(NotifyPropertyModified(CScriptObject*,IProperty*)));
|
}
|
||||||
|
|
||||||
|
void CPropertyView::ClearProperties()
|
||||||
|
{
|
||||||
|
mpObject = nullptr;
|
||||||
|
mpModel->ConfigureScript(nullptr, nullptr, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPropertyView::SetIntrinsicProperties(CStructRef InProperties)
|
void CPropertyView::SetIntrinsicProperties(CStructRef InProperties)
|
||||||
{
|
{
|
||||||
mpObject = nullptr;
|
mpObject = nullptr;
|
||||||
mpModel->SetBoldModifiedProperties(false); // todo, we prob want this, but can't set default properties on non script yet
|
|
||||||
mpModel->ConfigureIntrinsic(nullptr, InProperties.Property(), InProperties.DataPointer());
|
mpModel->ConfigureIntrinsic(nullptr, InProperties.Property(), InProperties.DataPointer());
|
||||||
SetPersistentEditors(QModelIndex());
|
SetPersistentEditors(QModelIndex());
|
||||||
}
|
}
|
||||||
|
@ -102,7 +116,7 @@ void CPropertyView::SetIntrinsicProperties(CStructRef InProperties)
|
||||||
void CPropertyView::SetInstance(CScriptObject *pObj)
|
void CPropertyView::SetInstance(CScriptObject *pObj)
|
||||||
{
|
{
|
||||||
mpObject = pObj;
|
mpObject = pObj;
|
||||||
mpModel->SetBoldModifiedProperties(mpEditor ? (mpEditor->CurrentGame() > EGame::Prime) : true);
|
mpModel->SetBoldModifiedProperties(gpEdApp->CurrentGame() > EGame::Prime);
|
||||||
|
|
||||||
if (pObj)
|
if (pObj)
|
||||||
mpModel->ConfigureScript(pObj->Area()->Entry()->Project(), pObj->Template()->Properties(), pObj);
|
mpModel->ConfigureScript(pObj->Area()->Entry()->Project(), pObj->Template()->Properties(), pObj);
|
||||||
|
@ -121,7 +135,7 @@ void CPropertyView::SetInstance(CScriptObject *pObj)
|
||||||
void CPropertyView::UpdateEditorProperties(const QModelIndex& rkParent)
|
void CPropertyView::UpdateEditorProperties(const QModelIndex& rkParent)
|
||||||
{
|
{
|
||||||
// Check what game this is
|
// Check what game this is
|
||||||
EGame Game = mpEditor->CurrentGame();
|
EGame Game = gpEdApp->CurrentGame();
|
||||||
|
|
||||||
// Iterate over all properties and update if they're an editor property.
|
// Iterate over all properties and update if they're an editor property.
|
||||||
for (int iRow = 0; iRow < mpModel->rowCount(rkParent); iRow++)
|
for (int iRow = 0; iRow < mpModel->rowCount(rkParent); iRow++)
|
||||||
|
@ -179,8 +193,7 @@ void CPropertyView::SetPersistentEditors(const QModelIndex& rkParent)
|
||||||
|
|
||||||
if (pProp->Type() == EPropertyType::AnimationSet)
|
if (pProp->Type() == EPropertyType::AnimationSet)
|
||||||
{
|
{
|
||||||
EGame Game = mpObject->Area()->Game();
|
Type = mpDelegate->DetermineCharacterPropType(pProp->Game(), ChildIndex);
|
||||||
Type = mpDelegate->DetermineCharacterPropType(Game, ChildIndex);
|
|
||||||
IsAnimSet = true;
|
IsAnimSet = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,6 +250,10 @@ void CPropertyView::OnPropertyModified(const QModelIndex& rkIndex)
|
||||||
ClosePersistentEditors(rkIndex);
|
ClosePersistentEditors(rkIndex);
|
||||||
SetPersistentEditors(rkIndex);
|
SetPersistentEditors(rkIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scrollTo(rkIndex);
|
||||||
|
emit PropertyModified(rkIndex);
|
||||||
|
emit PropertyModified(pProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPropertyView::RefreshView()
|
void CPropertyView::RefreshView()
|
||||||
|
@ -260,7 +277,7 @@ void CPropertyView::CreateContextMenu(const QPoint& rkPos)
|
||||||
Menu.addAction(mpEditTemplateAction);
|
Menu.addAction(mpEditTemplateAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mpEditor->CurrentGame() >= EGame::EchoesDemo)
|
if (gpEdApp->CurrentGame() >= EGame::EchoesDemo)
|
||||||
{
|
{
|
||||||
Menu.addAction(mpShowNameValidityAction);
|
Menu.addAction(mpShowNameValidityAction);
|
||||||
}
|
}
|
||||||
|
@ -297,7 +314,8 @@ void CPropertyView::ToggleShowNameValidity(bool ShouldShow)
|
||||||
|
|
||||||
void CPropertyView::EditPropertyTemplate()
|
void CPropertyView::EditPropertyTemplate()
|
||||||
{
|
{
|
||||||
CTemplateEditDialog Dialog(mpMenuProperty, mpEditor);
|
QMainWindow* pParentWindow = UICommon::FindAncestor<QMainWindow>(this);
|
||||||
|
CTemplateEditDialog Dialog(mpMenuProperty, pParentWindow);
|
||||||
connect(&Dialog, SIGNAL(PerformedTypeConversion()), this, SLOT(RefreshView()));
|
connect(&Dialog, SIGNAL(PerformedTypeConversion()), this, SLOT(RefreshView()));
|
||||||
Dialog.exec();
|
Dialog.exec();
|
||||||
}
|
}
|
||||||
|
@ -305,21 +323,21 @@ void CPropertyView::EditPropertyTemplate()
|
||||||
|
|
||||||
void CPropertyView::GenerateNamesForProperty()
|
void CPropertyView::GenerateNamesForProperty()
|
||||||
{
|
{
|
||||||
CGeneratePropertyNamesDialog* pDialog = mpEditor->NameGeneratorDialog();
|
CGeneratePropertyNamesDialog* pDialog = gpEdApp->WorldEditor()->NameGeneratorDialog();
|
||||||
pDialog->AddToIDPool(mpMenuProperty);
|
pDialog->AddToIDPool(mpMenuProperty);
|
||||||
pDialog->show();
|
pDialog->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPropertyView::GenerateNamesForSiblings()
|
void CPropertyView::GenerateNamesForSiblings()
|
||||||
{
|
{
|
||||||
CGeneratePropertyNamesDialog* pDialog = mpEditor->NameGeneratorDialog();
|
CGeneratePropertyNamesDialog* pDialog = gpEdApp->WorldEditor()->NameGeneratorDialog();
|
||||||
pDialog->AddChildrenToIDPool(mpMenuProperty->Parent(), false);
|
pDialog->AddChildrenToIDPool(mpMenuProperty->Parent(), false);
|
||||||
pDialog->show();
|
pDialog->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPropertyView::GenerateNamesForChildren()
|
void CPropertyView::GenerateNamesForChildren()
|
||||||
{
|
{
|
||||||
CGeneratePropertyNamesDialog* pDialog = mpEditor->NameGeneratorDialog();
|
CGeneratePropertyNamesDialog* pDialog = gpEdApp->WorldEditor()->NameGeneratorDialog();
|
||||||
pDialog->AddChildrenToIDPool(mpMenuProperty, false);
|
pDialog->AddChildrenToIDPool(mpMenuProperty, false);
|
||||||
pDialog->show();
|
pDialog->show();
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,25 +10,27 @@ class CPropertyView : public QTreeView
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
CWorldEditor *mpEditor;
|
CPropertyModel* mpModel;
|
||||||
CPropertyModel *mpModel;
|
CPropertyDelegate* mpDelegate;
|
||||||
CPropertyDelegate *mpDelegate;
|
CScriptObject* mpObject;
|
||||||
CScriptObject *mpObject;
|
|
||||||
|
|
||||||
IProperty *mpMenuProperty;
|
IProperty* mpMenuProperty;
|
||||||
QAction *mpShowNameValidityAction;
|
QAction* mpShowNameValidityAction;
|
||||||
QAction *mpEditTemplateAction;
|
QAction* mpEditTemplateAction;
|
||||||
QAction *mpGenNamesForPropertyAction;
|
QAction* mpGenNamesForPropertyAction;
|
||||||
QAction *mpGenNamesForSiblingsAction;
|
QAction* mpGenNamesForSiblingsAction;
|
||||||
QAction *mpGenNamesForChildrenAction;
|
QAction* mpGenNamesForChildrenAction;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CPropertyView(QWidget *pParent = 0);
|
CPropertyView(QWidget* pParent = 0);
|
||||||
void setModel(QAbstractItemModel *pModel);
|
void setModel(QAbstractItemModel* pModel);
|
||||||
bool event(QEvent *pEvent);
|
bool event(QEvent* pEvent);
|
||||||
void SetEditor(CWorldEditor *pEditor);
|
int sizeHintForColumn(int Column) const;
|
||||||
|
|
||||||
|
void SetEditor(IEditor* pEditor);
|
||||||
|
void ClearProperties();
|
||||||
void SetIntrinsicProperties(CStructRef InProperties);
|
void SetIntrinsicProperties(CStructRef InProperties);
|
||||||
void SetInstance(CScriptObject *pObj);
|
void SetInstance(CScriptObject* pObj);
|
||||||
void UpdateEditorProperties(const QModelIndex& rkParent);
|
void UpdateEditorProperties(const QModelIndex& rkParent);
|
||||||
|
|
||||||
inline CPropertyModel* PropertyModel() const { return mpModel; }
|
inline CPropertyModel* PropertyModel() const { return mpModel; }
|
||||||
|
@ -46,6 +48,10 @@ public slots:
|
||||||
void GenerateNamesForProperty();
|
void GenerateNamesForProperty();
|
||||||
void GenerateNamesForSiblings();
|
void GenerateNamesForSiblings();
|
||||||
void GenerateNamesForChildren();
|
void GenerateNamesForChildren();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void PropertyModified(const QModelIndex& kIndex);
|
||||||
|
void PropertyModified(IProperty* pProperty);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CPROPERTYVIEW_H
|
#endif // CPROPERTYVIEW_H
|
||||||
|
|
|
@ -8,7 +8,9 @@
|
||||||
#include "Editor/Undo/CMoveResourceCommand.h"
|
#include "Editor/Undo/CMoveResourceCommand.h"
|
||||||
#include "Editor/Undo/CRenameDirectoryCommand.h"
|
#include "Editor/Undo/CRenameDirectoryCommand.h"
|
||||||
#include "Editor/Undo/CRenameResourceCommand.h"
|
#include "Editor/Undo/CRenameResourceCommand.h"
|
||||||
|
#include "Editor/Undo/CSaveStoreCommand.h"
|
||||||
#include "Editor/Undo/ICreateDeleteDirectoryCommand.h"
|
#include "Editor/Undo/ICreateDeleteDirectoryCommand.h"
|
||||||
|
#include "Editor/Undo/ICreateDeleteResourceCommand.h"
|
||||||
#include <Core/GameProject/AssetNameGeneration.h>
|
#include <Core/GameProject/AssetNameGeneration.h>
|
||||||
#include <Core/GameProject/CAssetNameMap.h>
|
#include <Core/GameProject/CAssetNameMap.h>
|
||||||
|
|
||||||
|
@ -28,6 +30,7 @@ CResourceBrowser::CResourceBrowser(QWidget *pParent)
|
||||||
, mEditorStore(false)
|
, mEditorStore(false)
|
||||||
, mAssetListMode(false)
|
, mAssetListMode(false)
|
||||||
, mSearching(false)
|
, mSearching(false)
|
||||||
|
, mpAddMenu(nullptr)
|
||||||
, mpInspectedEntry(nullptr)
|
, mpInspectedEntry(nullptr)
|
||||||
{
|
{
|
||||||
mpUI->setupUi(this);
|
mpUI->setupUi(this);
|
||||||
|
@ -150,7 +153,6 @@ CResourceBrowser::CResourceBrowser(QWidget *pParent)
|
||||||
connect(mpUI->ResourceTreeButton, SIGNAL(pressed()), this, SLOT(SetResourceTreeView()));
|
connect(mpUI->ResourceTreeButton, SIGNAL(pressed()), this, SLOT(SetResourceTreeView()));
|
||||||
connect(mpUI->ResourceListButton, SIGNAL(pressed()), this, SLOT(SetResourceListView()));
|
connect(mpUI->ResourceListButton, SIGNAL(pressed()), this, SLOT(SetResourceListView()));
|
||||||
connect(mpUI->SortComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSortModeChanged(int)));
|
connect(mpUI->SortComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSortModeChanged(int)));
|
||||||
connect(mpUI->NewFolderButton, SIGNAL(pressed()), this, SLOT(CreateDirectory()));
|
|
||||||
connect(mpUI->ClearButton, SIGNAL(pressed()), this, SLOT(OnClearButtonPressed()));
|
connect(mpUI->ClearButton, SIGNAL(pressed()), this, SLOT(OnClearButtonPressed()));
|
||||||
|
|
||||||
connect(mpUI->DirectoryTreeView, SIGNAL(clicked(QModelIndex)), this, SLOT(OnDirectorySelectionChanged(QModelIndex)));
|
connect(mpUI->DirectoryTreeView, SIGNAL(clicked(QModelIndex)), this, SLOT(OnDirectorySelectionChanged(QModelIndex)));
|
||||||
|
@ -281,6 +283,50 @@ void CResourceBrowser::CreateFilterCheckboxes()
|
||||||
mpFilterBoxesLayout->addSpacerItem(pSpacer);
|
mpFilterBoxesLayout->addSpacerItem(pSpacer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CResourceBrowser::CreateAddMenu()
|
||||||
|
{
|
||||||
|
// Delete any existing menu
|
||||||
|
if (mpAddMenu)
|
||||||
|
{
|
||||||
|
delete mpAddMenu;
|
||||||
|
mpAddMenu = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new one, only if we have a valid resource store.
|
||||||
|
if (mpStore)
|
||||||
|
{
|
||||||
|
mpAddMenu = new QMenu(this);
|
||||||
|
mpAddMenu->addAction("New Folder", this, SLOT(CreateDirectory()));
|
||||||
|
mpAddMenu->addSeparator();
|
||||||
|
|
||||||
|
QMenu* pCreateMenu = new QMenu("Create...");
|
||||||
|
mpAddMenu->addMenu(pCreateMenu);
|
||||||
|
AddCreateAssetMenuActions(pCreateMenu);
|
||||||
|
|
||||||
|
mpUI->AddButton->setMenu(mpAddMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
mpUI->AddButton->setEnabled( mpAddMenu != nullptr );
|
||||||
|
}
|
||||||
|
|
||||||
|
void CResourceBrowser::AddCreateAssetMenuActions(QMenu* pMenu)
|
||||||
|
{
|
||||||
|
std::list<CResTypeInfo*> TypeInfos;
|
||||||
|
CResTypeInfo::GetAllTypesInGame(mpStore->Game(), TypeInfos);
|
||||||
|
|
||||||
|
for (auto Iter = TypeInfos.begin(); Iter != TypeInfos.end(); Iter++)
|
||||||
|
{
|
||||||
|
CResTypeInfo* pTypeInfo = *Iter;
|
||||||
|
|
||||||
|
if (pTypeInfo->CanBeCreated())
|
||||||
|
{
|
||||||
|
QString TypeName = TO_QSTRING( pTypeInfo->TypeName() );
|
||||||
|
QAction* pAction = pMenu->addAction(TypeName, this, SLOT(OnCreateAssetAction()));
|
||||||
|
pAction->setProperty("TypeInfo", QVariant((int) pTypeInfo->Type()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool CResourceBrowser::RenameResource(CResourceEntry *pEntry, const TString& rkNewName)
|
bool CResourceBrowser::RenameResource(CResourceEntry *pEntry, const TString& rkNewName)
|
||||||
{
|
{
|
||||||
if (pEntry->Name() == rkNewName)
|
if (pEntry->Name() == rkNewName)
|
||||||
|
@ -302,7 +348,11 @@ bool CResourceBrowser::RenameResource(CResourceEntry *pEntry, const TString& rkN
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everything seems to be valid; proceed with the rename
|
// Everything seems to be valid; proceed with the rename
|
||||||
|
mUndoStack.beginMacro("Rename Resource");
|
||||||
|
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||||
mUndoStack.push( new CRenameResourceCommand(pEntry, rkNewName) );
|
mUndoStack.push( new CRenameResourceCommand(pEntry, rkNewName) );
|
||||||
|
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||||
|
mUndoStack.endMacro();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,7 +375,11 @@ bool CResourceBrowser::RenameDirectory(CVirtualDirectory *pDir, const TString& r
|
||||||
}
|
}
|
||||||
|
|
||||||
// No conflicts, proceed with the rename
|
// No conflicts, proceed with the rename
|
||||||
|
mUndoStack.beginMacro("Rename Directory");
|
||||||
|
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||||
mUndoStack.push( new CRenameDirectoryCommand(pDir, rkNewName) );
|
mUndoStack.push( new CRenameDirectoryCommand(pDir, rkNewName) );
|
||||||
|
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||||
|
mUndoStack.endMacro();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,6 +441,7 @@ bool CResourceBrowser::MoveResources(const QList<CResourceEntry*>& rkResources,
|
||||||
if (!ValidResources.isEmpty() || !ValidDirs.isEmpty())
|
if (!ValidResources.isEmpty() || !ValidDirs.isEmpty())
|
||||||
{
|
{
|
||||||
mUndoStack.beginMacro("Move Resources");
|
mUndoStack.beginMacro("Move Resources");
|
||||||
|
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||||
|
|
||||||
foreach (CVirtualDirectory *pDir, ValidDirs)
|
foreach (CVirtualDirectory *pDir, ValidDirs)
|
||||||
mUndoStack.push( new CMoveDirectoryCommand(mpStore, pDir, pNewDir) );
|
mUndoStack.push( new CMoveDirectoryCommand(mpStore, pDir, pNewDir) );
|
||||||
|
@ -394,12 +449,71 @@ bool CResourceBrowser::MoveResources(const QList<CResourceEntry*>& rkResources,
|
||||||
foreach (CResourceEntry *pEntry, ValidResources)
|
foreach (CResourceEntry *pEntry, ValidResources)
|
||||||
mUndoStack.push( new CMoveResourceCommand(pEntry, pNewDir) );
|
mUndoStack.push( new CMoveResourceCommand(pEntry, pNewDir) );
|
||||||
|
|
||||||
|
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||||
mUndoStack.endMacro();
|
mUndoStack.endMacro();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CResourceEntry* CResourceBrowser::CreateNewResource(EResourceType Type,
|
||||||
|
TString Name /*= ""*/,
|
||||||
|
CVirtualDirectory* pDir /*= nullptr*/,
|
||||||
|
CAssetID ID /*= CAssetID()*/)
|
||||||
|
{
|
||||||
|
if (!pDir)
|
||||||
|
{
|
||||||
|
pDir = mpSelectedDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new asset ID. Sanity check to make sure the ID is unused.
|
||||||
|
while (!ID.IsValid() || mpStore->FindEntry(ID) != nullptr)
|
||||||
|
{
|
||||||
|
ID = CAssetID::RandomID( mpStore->Game() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boring generic default name - user will immediately be prompted to change this
|
||||||
|
TString BaseName = Name;
|
||||||
|
|
||||||
|
if (BaseName.IsEmpty())
|
||||||
|
{
|
||||||
|
BaseName = TString::Format(
|
||||||
|
"New %s", *CResTypeInfo::FindTypeInfo(Type)->TypeName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Name = BaseName;
|
||||||
|
int Num = 0;
|
||||||
|
|
||||||
|
while (pDir->FindChildResource(Name, Type) != nullptr)
|
||||||
|
{
|
||||||
|
Num++;
|
||||||
|
Name = TString::Format("%s (%d)", *BaseName, Num);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the actual resource
|
||||||
|
CResourceEntry* pEntry = mpStore->CreateNewResource(ID, Type, pDir->FullPath(), Name);
|
||||||
|
|
||||||
|
// Push undo command
|
||||||
|
mUndoStack.beginMacro("Create Resource");
|
||||||
|
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||||
|
mUndoStack.push( new CCreateResourceCommand(pEntry) );
|
||||||
|
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||||
|
mUndoStack.endMacro();
|
||||||
|
|
||||||
|
pEntry->Save();
|
||||||
|
|
||||||
|
// Select new resource so user can enter a name
|
||||||
|
QModelIndex Index = mpModel->GetIndexForEntry(pEntry);
|
||||||
|
ASSERT(Index.isValid());
|
||||||
|
|
||||||
|
QModelIndex ProxyIndex = mpProxyModel->mapFromSource(Index);
|
||||||
|
mpUI->ResourceTableView->selectionModel()->select(ProxyIndex, QItemSelectionModel::ClearAndSelect);
|
||||||
|
mpUI->ResourceTableView->edit(ProxyIndex);
|
||||||
|
|
||||||
|
return pEntry;
|
||||||
|
}
|
||||||
|
|
||||||
bool CResourceBrowser::eventFilter(QObject *pWatched, QEvent *pEvent)
|
bool CResourceBrowser::eventFilter(QObject *pWatched, QEvent *pEvent)
|
||||||
{
|
{
|
||||||
if (pWatched == mpUI->ResourceTableView)
|
if (pWatched == mpUI->ResourceTableView)
|
||||||
|
@ -498,6 +612,23 @@ void CResourceBrowser::OnSortModeChanged(int Index)
|
||||||
mpProxyModel->SetSortMode(Mode);
|
mpProxyModel->SetSortMode(Mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CResourceBrowser::OnCreateAssetAction()
|
||||||
|
{
|
||||||
|
// Attempt to retrieve the asset type from the sender. If successful, create the asset.
|
||||||
|
QAction* pSender = qobject_cast<QAction*>(sender());
|
||||||
|
|
||||||
|
if (pSender)
|
||||||
|
{
|
||||||
|
bool Ok;
|
||||||
|
EResourceType Type = (EResourceType) pSender->property("TypeInfo").toInt(&Ok);
|
||||||
|
|
||||||
|
if (Ok)
|
||||||
|
{
|
||||||
|
CreateNewResource(Type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool CResourceBrowser::CreateDirectory()
|
bool CResourceBrowser::CreateDirectory()
|
||||||
{
|
{
|
||||||
if (mpSelectedDir)
|
if (mpSelectedDir)
|
||||||
|
@ -513,8 +644,12 @@ bool CResourceBrowser::CreateDirectory()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push create command to actually create the directory
|
// Push create command to actually create the directory
|
||||||
|
mUndoStack.beginMacro("Create Directory");
|
||||||
|
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||||
CCreateDirectoryCommand *pCmd = new CCreateDirectoryCommand(mpStore, mpSelectedDir->FullPath(), DirName);
|
CCreateDirectoryCommand *pCmd = new CCreateDirectoryCommand(mpStore, mpSelectedDir->FullPath(), DirName);
|
||||||
mUndoStack.push(pCmd);
|
mUndoStack.push(pCmd);
|
||||||
|
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||||
|
mUndoStack.endMacro();
|
||||||
|
|
||||||
// Now fetch the new directory and start editing it so the user can enter a name
|
// Now fetch the new directory and start editing it so the user can enter a name
|
||||||
CVirtualDirectory *pNewDir = mpSelectedDir->FindChildDirectory(DirName, false);
|
CVirtualDirectory *pNewDir = mpSelectedDir->FindChildDirectory(DirName, false);
|
||||||
|
@ -537,28 +672,101 @@ bool CResourceBrowser::CreateDirectory()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CResourceBrowser::DeleteDirectories(const QList<CVirtualDirectory*>& rkDirs)
|
bool CResourceBrowser::Delete(QVector<CResourceEntry*> Resources, QVector<CVirtualDirectory*> Directories)
|
||||||
{
|
{
|
||||||
QList<CVirtualDirectory*> DeletableDirs;
|
// Don't delete any resources/directories that are still referenced.
|
||||||
|
// This is kind of a hack but there's no good way to clear out these references right now.
|
||||||
|
QString ErrorPaths;
|
||||||
|
|
||||||
foreach (CVirtualDirectory *pDir, rkDirs)
|
for (int DirIdx = 0; DirIdx < Directories.size(); DirIdx++)
|
||||||
{
|
{
|
||||||
if (pDir && pDir->IsEmpty(true))
|
if (!Directories[DirIdx]->IsSafeToDelete())
|
||||||
DeletableDirs << pDir;
|
{
|
||||||
|
ErrorPaths += TO_QSTRING( Directories[DirIdx]->FullPath() ) + '\n';
|
||||||
|
Directories.removeAt(DirIdx);
|
||||||
|
DirIdx--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DeletableDirs.size() > 0)
|
for (int ResIdx = 0; ResIdx < Resources.size(); ResIdx++)
|
||||||
{
|
{
|
||||||
mUndoStack.beginMacro("Delete Directories");
|
if (Resources[ResIdx]->IsLoaded() && Resources[ResIdx]->Resource()->IsReferenced())
|
||||||
|
{
|
||||||
|
ErrorPaths += TO_QSTRING( Resources[ResIdx]->CookedAssetPath(true) ) + '\n';
|
||||||
|
Resources.removeAt(ResIdx);
|
||||||
|
ResIdx--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (CVirtualDirectory *pDir, DeletableDirs)
|
if (!ErrorPaths.isEmpty())
|
||||||
|
{
|
||||||
|
// Remove trailing newline
|
||||||
|
ErrorPaths.chop(1);
|
||||||
|
UICommon::ErrorMsg(this, QString("The following resources/directories are still referenced and cannot be deleted:\n\n%1")
|
||||||
|
.arg(ErrorPaths));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gather a complete list of resources in subdirectories
|
||||||
|
for (int DirIdx = 0; DirIdx < Directories.size(); DirIdx++)
|
||||||
|
{
|
||||||
|
CVirtualDirectory* pDir = Directories[DirIdx];
|
||||||
|
Resources.reserve( Resources.size() + pDir->NumResources() );
|
||||||
|
Directories.reserve( Directories.size() + pDir->NumSubdirectories() );
|
||||||
|
|
||||||
|
for (uint ResourceIdx = 0; ResourceIdx < pDir->NumResources(); ResourceIdx++)
|
||||||
|
Resources << pDir->ResourceByIndex(ResourceIdx);
|
||||||
|
|
||||||
|
for (uint SubdirIdx = 0; SubdirIdx < pDir->NumSubdirectories(); SubdirIdx++)
|
||||||
|
Directories << pDir->SubdirectoryByIndex(SubdirIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit if we have nothing to do.
|
||||||
|
if (Resources.isEmpty() && Directories.isEmpty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Allow the user to confirm before proceeding.
|
||||||
|
QString ConfirmMsg = QString("Are you sure you want to permanently delete ");
|
||||||
|
|
||||||
|
if (Resources.size() > 0)
|
||||||
|
{
|
||||||
|
ConfirmMsg += QString("%1 resource%2").arg(Resources.size()).arg(Resources.size() == 1 ? "" : "s");
|
||||||
|
|
||||||
|
if (Directories.size() > 0)
|
||||||
|
{
|
||||||
|
ConfirmMsg += " and ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Directories.size() > 0)
|
||||||
|
{
|
||||||
|
ConfirmMsg += QString("%1 %2").arg(Directories.size()).arg(Directories.size() == 1 ? "directory" : "directories");
|
||||||
|
}
|
||||||
|
ConfirmMsg += "?";
|
||||||
|
|
||||||
|
if (UICommon::YesNoQuestion(this, "Warning", ConfirmMsg))
|
||||||
|
{
|
||||||
|
// Note that the undo stack will undo actions in the reverse order they are pushed
|
||||||
|
// So we need to push commands last that we want to be undone first
|
||||||
|
// We want to delete subdirectories first, then parent directories, then resources
|
||||||
|
mUndoStack.beginMacro("Delete");
|
||||||
|
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||||
|
|
||||||
|
// Delete resources first.
|
||||||
|
foreach (CResourceEntry* pEntry, Resources)
|
||||||
|
mUndoStack.push( new CDeleteResourceCommand(pEntry) );
|
||||||
|
|
||||||
|
// Now delete directories in reverse order (so subdirectories delete first)
|
||||||
|
for (int DirIdx = Directories.size()-1; DirIdx >= 0; DirIdx--)
|
||||||
|
{
|
||||||
|
CVirtualDirectory* pDir = Directories[DirIdx];
|
||||||
mUndoStack.push( new CDeleteDirectoryCommand(mpStore, pDir->Parent()->FullPath(), pDir->Name()) );
|
mUndoStack.push( new CDeleteDirectoryCommand(mpStore, pDir->Parent()->FullPath(), pDir->Name()) );
|
||||||
|
}
|
||||||
|
|
||||||
|
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||||
mUndoStack.endMacro();
|
mUndoStack.endMacro();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
else return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CResourceBrowser::OnSearchStringChanged(QString SearchString)
|
void CResourceBrowser::OnSearchStringChanged(QString SearchString)
|
||||||
|
@ -685,7 +893,8 @@ void CResourceBrowser::UpdateStore()
|
||||||
mpUI->SearchBar->clear();
|
mpUI->SearchBar->clear();
|
||||||
mSearching = false;
|
mSearching = false;
|
||||||
|
|
||||||
// Refresh type filter list
|
// Refresh project-specific UI
|
||||||
|
CreateAddMenu();
|
||||||
CreateFilterCheckboxes();
|
CreateFilterCheckboxes();
|
||||||
|
|
||||||
// Refresh directory tree
|
// Refresh directory tree
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue