Merge branch 'StringEditing'
This commit is contained in:
commit
1baa48de34
|
@ -1 +1 @@
|
|||
Subproject commit 47d83075e0fb20786ad28c2e945a204ed1837856
|
||||
Subproject commit 3c6a40742551d7afd0737d1293d036df69f34ec6
|
|
@ -26,6 +26,7 @@ about
|
|||
above
|
||||
absolute
|
||||
absorb
|
||||
accel
|
||||
acceleration
|
||||
accumulate
|
||||
achievement
|
||||
|
@ -962,6 +963,7 @@ orb
|
|||
orbit
|
||||
orbitable
|
||||
order
|
||||
organic
|
||||
orient
|
||||
orientation
|
||||
origin
|
||||
|
@ -1260,6 +1262,8 @@ shoulder
|
|||
show
|
||||
shredder
|
||||
shrink
|
||||
shrub
|
||||
shrubbery
|
||||
shut
|
||||
shutdown
|
||||
side
|
||||
|
@ -1555,6 +1559,7 @@ while
|
|||
whip
|
||||
white
|
||||
widget
|
||||
width
|
||||
window
|
||||
wing
|
||||
with
|
||||
|
|
|
@ -109,6 +109,7 @@ HEADERS += \
|
|||
Resource/Script/CScriptObject.h \
|
||||
Resource/Script/CScriptTemplate.h \
|
||||
Resource/Script/EVolumeShape.h \
|
||||
Resource/StringTable/CStringTable.h \
|
||||
Resource/CCollisionMesh.h \
|
||||
Resource/CCollisionMeshGroup.h \
|
||||
Resource/CFont.h \
|
||||
|
@ -117,8 +118,6 @@ HEADERS += \
|
|||
Resource/CMaterialPass.h \
|
||||
Resource/CMaterialSet.h \
|
||||
Resource/CResource.h \
|
||||
Resource/CScan.h \
|
||||
Resource/CStringTable.h \
|
||||
Resource/CTexture.h \
|
||||
Resource/CWorld.h \
|
||||
Resource/EResType.h \
|
||||
|
@ -246,7 +245,18 @@ HEADERS += \
|
|||
Resource/Script/Property/CGuidProperty.h \
|
||||
Resource/Script/CGameTemplate.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
|
||||
SOURCES += \
|
||||
|
@ -357,7 +367,15 @@ SOURCES += \
|
|||
Resource/Script/Property/CFlagsProperty.cpp \
|
||||
Resource/Script/CGameTemplate.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_DIR = $$EXTERNALS_DIR/CodeGen
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
#include "CResourceIterator.h"
|
||||
#include "Core/Resource/CAudioMacro.h"
|
||||
#include "Core/Resource/CFont.h"
|
||||
#include "Core/Resource/CScan.h"
|
||||
#include "Core/Resource/CWorld.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 <Common/Math/MathUtil.h>
|
||||
|
||||
|
@ -311,15 +312,20 @@ void GenerateAssetNames(CGameProject *pProj)
|
|||
ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), ScanName);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (pInst->ObjectTypeID() == 0x17 || pInst->ObjectTypeID() == FOURCC('MEMO'))
|
||||
{
|
||||
|
@ -585,7 +591,7 @@ void GenerateAssetNames(CGameProject *pProj)
|
|||
TString String;
|
||||
|
||||
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())
|
||||
{
|
||||
|
@ -609,16 +615,10 @@ void GenerateAssetNames(CGameProject *pProj)
|
|||
CScan *pScan = (CScan*) It->Load();
|
||||
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())
|
||||
{
|
||||
CStringTable *pString = pScan->ScanText();
|
||||
CAssetID StringID = pScan->ScanStringPropertyRef().Get();
|
||||
CStringTable *pString = (CStringTable*) gpResourceStore->LoadResource(StringID, EResourceType::StringTable);
|
||||
if (pString) ScanName = pString->Entry()->Name();
|
||||
}
|
||||
|
||||
|
@ -626,13 +626,14 @@ void GenerateAssetNames(CGameProject *pProj)
|
|||
|
||||
if (!ScanName.IsEmpty() && pProj->Game() <= EGame::Prime)
|
||||
{
|
||||
CAssetID FrameID = pScan->GuiFrame();
|
||||
CResourceEntry *pEntry = pStore->FindEntry(FrameID);
|
||||
const SScanParametersMP1& kParms = *static_cast<SScanParametersMP1*>(pScan->ScanData().DataPointer());
|
||||
|
||||
CResourceEntry *pEntry = pStore->FindEntry(kParms.GuiFrame);
|
||||
if (pEntry) ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), "ScanFrame");
|
||||
|
||||
for (uint32 iImg = 0; iImg < 4; iImg++)
|
||||
{
|
||||
CAssetID ImageID = pScan->ScanImage(iImg);
|
||||
CAssetID ImageID = kParms.ScanImages[iImg].Texture;
|
||||
CResourceEntry *pImgEntry = pStore->FindEntry(ImageID);
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
IDependencyNode* IDependencyNode::ArchiveConstructor(EDependencyNodeType Type)
|
||||
{
|
||||
|
@ -149,76 +213,10 @@ CScriptInstanceDependency* CScriptInstanceDependency::BuildTree(CScriptObject *p
|
|||
{
|
||||
CScriptInstanceDependency *pInst = new CScriptInstanceDependency();
|
||||
pInst->mObjectType = pInstance->ObjectTypeID();
|
||||
ParseStructDependencies(pInst, pInstance, pInstance->Template()->Properties());
|
||||
pInst->ParseProperties(pInstance->Area()->Entry(), pInstance->Template()->Properties(), pInstance->PropertyData());
|
||||
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 ************
|
||||
EDependencyNodeType CSetCharacterDependency::Type() const
|
||||
{
|
||||
|
|
|
@ -39,6 +39,7 @@ public:
|
|||
virtual void Serialize(IArchive& rArc) = 0;
|
||||
virtual void GetAllResourceReferences(std::set<CAssetID>& rOutSet) const;
|
||||
virtual bool HasDependency(const CAssetID& rkID) const;
|
||||
void ParseProperties(CResourceEntry* pParentEntry, CStructProperty* pProperties, void* pData);
|
||||
|
||||
// Serialization constructor
|
||||
static IDependencyNode* ArchiveConstructor(EDependencyNodeType Type);
|
||||
|
@ -144,8 +145,6 @@ public:
|
|||
|
||||
// Static
|
||||
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.
|
||||
|
|
|
@ -198,16 +198,16 @@ bool CGameExporter::ExtractDiscData()
|
|||
if (IsWii)
|
||||
{
|
||||
// Extract crypto files
|
||||
if (!pDataPartition->extractCryptoFiles(*AbsDiscDir.ToUTF16(), Context))
|
||||
if (!pDataPartition->extractCryptoFiles(ToWChar(AbsDiscDir), Context))
|
||||
return false;
|
||||
|
||||
// Extract disc header files
|
||||
if (!mpDisc->extractDiscHeaderFiles(*AbsDiscDir.ToUTF16(), Context))
|
||||
if (!mpDisc->extractDiscHeaderFiles(ToWChar(AbsDiscDir), Context))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract system files
|
||||
if (!pDataPartition->extractSysFiles(*AbsDiscDir.ToUTF16(), Context))
|
||||
if (!pDataPartition->extractSysFiles(ToWChar(AbsDiscDir), Context))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
@ -226,7 +226,7 @@ bool CGameExporter::ExtractDiscNodeRecursive(const nod::Node *pkNode, const TStr
|
|||
if (Iter->getKind() == nod::Node::Kind::File)
|
||||
{
|
||||
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 (FilePath.GetFileExtension().CaseInsensitiveCompare("pak"))
|
||||
|
@ -626,7 +626,7 @@ void CGameExporter::ExportResource(SResourceInstance& rRes)
|
|||
Name = rRes.ResourceID.ToString();
|
||||
#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
|
||||
pEntry->SetFlag(EResEntryFlag::IsBaseGameResource);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "CGameProject.h"
|
||||
#include "CResourceIterator.h"
|
||||
#include "IUIRelay.h"
|
||||
#include "Core/Resource/Script/CGameTemplate.h"
|
||||
#include <Common/Serialization/XML.h>
|
||||
|
@ -10,16 +11,12 @@ CGameProject::~CGameProject()
|
|||
{
|
||||
ASSERT(!mpResourceStore->IsCacheDirty());
|
||||
|
||||
if (gpResourceStore == mpResourceStore)
|
||||
if (gpResourceStore == mpResourceStore.get())
|
||||
gpResourceStore = nullptr;
|
||||
}
|
||||
|
||||
for (uint32 iPkg = 0; iPkg < mPackages.size(); iPkg++)
|
||||
delete mPackages[iPkg];
|
||||
|
||||
delete mpAudioManager;
|
||||
delete mpGameInfo;
|
||||
delete mpResourceStore;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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());
|
||||
TWideString DiscRoot = DiscDir(false).ToUTF16();
|
||||
TString DiscRoot = DiscDir(false);
|
||||
|
||||
if (!IsWiiBuild())
|
||||
{
|
||||
nod::DiscBuilderGCN Builder(*rkIsoPath.ToUTF16(), ProgressCallback);
|
||||
return Builder.buildFromDirectory(*DiscRoot) == nod::EBuildResult::Success;
|
||||
nod::DiscBuilderGCN Builder(ToWChar(rkIsoPath), ProgressCallback);
|
||||
return Builder.buildFromDirectory(ToWChar(DiscRoot)) == nod::EBuildResult::Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
nod::DiscBuilderWii Builder(*rkIsoPath.ToUTF16(), IsTrilogy(), ProgressCallback);
|
||||
return Builder.buildFromDirectory(*DiscRoot) == nod::EBuildResult::Success;
|
||||
nod::DiscBuilderWii Builder(ToWChar(rkIsoPath), IsTrilogy(), ProgressCallback);
|
||||
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)
|
||||
{
|
||||
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());
|
||||
|
||||
TWideString DiscRoot = DiscFilesystemRoot(false).ToUTF16();
|
||||
TString DiscRoot = DiscFilesystemRoot(false);
|
||||
|
||||
nod::DiscMergerWii Merger(*rkIsoPath.ToUTF16(), *pOriginalIso, IsTrilogy(), ProgressCallback);
|
||||
return Merger.mergeFromDirectory(*DiscRoot) == nod::EBuildResult::Success;
|
||||
nod::DiscMergerWii Merger(ToWChar(rkIsoPath), *pOriginalIso, IsTrilogy(), ProgressCallback);
|
||||
return Merger.mergeFromDirectory(ToWChar(DiscRoot)) == nod::EBuildResult::Success;
|
||||
}
|
||||
|
||||
void CGameProject::GetWorldList(std::list<CAssetID>& rOut) const
|
||||
|
@ -200,7 +197,7 @@ CGameProject* CGameProject::CreateProjectForExport(
|
|||
|
||||
pProj->mProjectRoot = rkProjRootDir;
|
||||
pProj->mProjectRoot.Replace("\\", "/");
|
||||
pProj->mpResourceStore = new CResourceStore(pProj);
|
||||
pProj->mpResourceStore = std::make_unique<CResourceStore>(pProj);
|
||||
pProj->mpGameInfo->LoadGameInfo(Game);
|
||||
return pProj;
|
||||
}
|
||||
|
@ -234,10 +231,14 @@ CGameProject* CGameProject::LoadProject(const TString& rkProjPath, IProgressNoti
|
|||
{
|
||||
// Load resource database
|
||||
pProgress->Report("Loading resource database");
|
||||
pProj->mpResourceStore = new CResourceStore(pProj);
|
||||
pProj->mpResourceStore = std::make_unique<CResourceStore>(pProj);
|
||||
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)
|
||||
{
|
||||
pProgress->Report("Validating resource database");
|
||||
|
@ -257,6 +258,7 @@ CGameProject* CGameProject::LoadProject(const TString& rkProjPath, IProgressNoti
|
|||
LoadSuccess = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!LoadSuccess)
|
||||
|
@ -267,6 +269,44 @@ CGameProject* CGameProject::LoadProject(const TString& rkProjPath, IProgressNoti
|
|||
|
||||
pProj->mProjFileLock.Lock(ProjPath);
|
||||
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->mpTweakManager->LoadTweaks();
|
||||
return pProj;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "Core/CAudioManager.h"
|
||||
#include "Core/IProgressNotifier.h"
|
||||
#include "Core/Resource/Script/CGameTemplate.h"
|
||||
#include "Core/Tweaks/CTweakManager.h"
|
||||
#include <Common/CAssetID.h>
|
||||
#include <Common/EGame.h>
|
||||
#include <Common/FileUtil.h>
|
||||
|
@ -18,6 +19,7 @@ namespace nod { class DiscWii; }
|
|||
enum class EProjectVersion
|
||||
{
|
||||
Initial,
|
||||
RawStrings,
|
||||
// Add new versions before this line
|
||||
|
||||
Max,
|
||||
|
@ -34,9 +36,10 @@ class CGameProject
|
|||
|
||||
TString mProjectRoot;
|
||||
std::vector<CPackage*> mPackages;
|
||||
CResourceStore *mpResourceStore;
|
||||
CGameInfo *mpGameInfo;
|
||||
CAudioManager *mpAudioManager;
|
||||
std::unique_ptr<CResourceStore> mpResourceStore;
|
||||
std::unique_ptr<CGameInfo> mpGameInfo;
|
||||
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
|
||||
// in multiple instances of PWE
|
||||
|
@ -51,14 +54,14 @@ class CGameProject
|
|||
, mBuildVersion(0.f)
|
||||
, mpResourceStore(nullptr)
|
||||
{
|
||||
mpGameInfo = new CGameInfo();
|
||||
mpAudioManager = new CAudioManager(this);
|
||||
mpGameInfo = std::make_unique<CGameInfo>();
|
||||
mpAudioManager = std::make_unique<CAudioManager>(this);
|
||||
mpTweakManager = std::make_unique<CTweakManager>(this);
|
||||
}
|
||||
|
||||
public:
|
||||
~CGameProject();
|
||||
|
||||
|
||||
bool Save();
|
||||
bool Serialize(IArchive& rArc);
|
||||
bool BuildISO(const TString& rkIsoPath, IProgressNotifier *pProgress);
|
||||
|
@ -81,6 +84,7 @@ public:
|
|||
// Directory Handling
|
||||
inline TString ProjectRoot() const { return mProjectRoot; }
|
||||
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 PackagesDir(bool Relative) const { return Relative ? "Packages/" : mProjectRoot + "Packages/"; }
|
||||
inline TString ResourcesDir(bool Relative) const { return Relative ? "Resources/" : mProjectRoot + "Resources/"; }
|
||||
|
@ -95,9 +99,10 @@ public:
|
|||
inline uint32 NumPackages() const { return mPackages.size(); }
|
||||
inline CPackage* PackageByIndex(uint32 Index) const { return mPackages[Index]; }
|
||||
inline void AddPackage(CPackage *pPackage) { mPackages.push_back(pPackage); }
|
||||
inline CResourceStore* ResourceStore() const { return mpResourceStore; }
|
||||
inline CGameInfo* GameInfo() const { return mpGameInfo; }
|
||||
inline CAudioManager* AudioManager() const { return mpAudioManager; }
|
||||
inline CResourceStore* ResourceStore() const { return mpResourceStore.get(); }
|
||||
inline CGameInfo* GameInfo() const { return mpGameInfo.get(); }
|
||||
inline CAudioManager* AudioManager() const { return mpAudioManager.get(); }
|
||||
inline CTweakManager* TweakManager() const { return mpTweakManager.get(); }
|
||||
inline EGame Game() const { return mGame; }
|
||||
inline ERegion Region() const { return mRegion; }
|
||||
inline TString GameID() const { return mGameID; }
|
||||
|
|
|
@ -30,7 +30,7 @@ TString COpeningBanner::EnglishGameName() const
|
|||
Banner.ReadBytes(NameBuffer.data(), MaxLen * CharSize);
|
||||
|
||||
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)
|
||||
|
@ -44,7 +44,7 @@ void COpeningBanner::SetEnglishGameName(const TString& rkName)
|
|||
if (mWii)
|
||||
{
|
||||
Banner.GoTo(0xB0);
|
||||
Banner.WriteWString(rkName.ToUTF16(), -1, false);
|
||||
Banner.Write16String(rkName.ToUTF16(), -1, false);
|
||||
PadCount = (MaxLen - rkName.Size()) * 2;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -59,6 +59,16 @@ void CPackage::UpdateDependencyCache() const
|
|||
mCacheDirty = false;
|
||||
}
|
||||
|
||||
void CPackage::MarkDirty()
|
||||
{
|
||||
if (!mNeedsRecook)
|
||||
{
|
||||
mNeedsRecook = true;
|
||||
Save();
|
||||
UpdateDependencyCache();
|
||||
}
|
||||
}
|
||||
|
||||
void CPackage::Cook(IProgressNotifier *pProgress)
|
||||
{
|
||||
SCOPED_TIMER(CookPackage);
|
||||
|
|
|
@ -60,6 +60,7 @@ public:
|
|||
void Serialize(IArchive& rArc);
|
||||
void AddResource(const TString& rkName, const CAssetID& rkID, const CFourCC& rkType);
|
||||
void UpdateDependencyCache() const;
|
||||
void MarkDirty();
|
||||
|
||||
void Cook(IProgressNotifier *pProgress);
|
||||
void CompareOriginalAssetList(const std::list<CAssetID>& rkNewList);
|
||||
|
@ -77,7 +78,6 @@ public:
|
|||
inline bool NeedsRecook() const { return mNeedsRecook; }
|
||||
|
||||
inline void SetPakName(TString NewName) { mPakName = NewName; }
|
||||
inline void MarkDirty() { mNeedsRecook = true; Save(); UpdateDependencyCache(); }
|
||||
};
|
||||
|
||||
#endif // CPACKAGE
|
||||
|
|
|
@ -40,6 +40,15 @@ CResourceEntry* CResourceEntry::CreateNewResource(CResourceStore *pStore, const
|
|||
pEntry->mpDirectory->AddChild("", pEntry);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -104,6 +113,9 @@ bool CResourceEntry::LoadMetadata()
|
|||
|
||||
bool CResourceEntry::SaveMetadata(bool ForceSave /*= false*/)
|
||||
{
|
||||
// Make sure we aren't saving a deleted resource
|
||||
ASSERT( !HasFlag(EResEntryFlag::MarkedForDeletion) );
|
||||
|
||||
if (mMetadataDirty || ForceSave)
|
||||
{
|
||||
TString Path = MetadataFilePath();
|
||||
|
@ -261,7 +273,7 @@ bool CResourceEntry::NeedsRecook() const
|
|||
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
|
||||
// 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.
|
||||
// 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;
|
||||
|
||||
// Save raw resource
|
||||
|
@ -298,8 +309,11 @@ bool CResourceEntry::Save(bool SkipCacheSave /*= false*/)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (FlagForRecook)
|
||||
{
|
||||
SetFlag(EResEntryFlag::NeedsRecook);
|
||||
}
|
||||
}
|
||||
|
||||
// This resource type doesn't have a raw format; save cooked instead
|
||||
else
|
||||
|
@ -321,13 +335,16 @@ bool CResourceEntry::Save(bool SkipCacheSave /*= false*/)
|
|||
if (!SkipCacheSave)
|
||||
{
|
||||
mpStore->ConditionalSaveStore();
|
||||
}
|
||||
|
||||
// Flag dirty any packages that contain this resource.
|
||||
if (FlagForRecook)
|
||||
{
|
||||
for (uint32 iPkg = 0; iPkg < mpStore->Project()->NumPackages(); iPkg++)
|
||||
{
|
||||
CPackage *pPkg = mpStore->Project()->PackageByIndex(iPkg);
|
||||
|
||||
if (pPkg->ContainsAsset(ID()))
|
||||
if (!pPkg->NeedsRecook() && pPkg->ContainsAsset(ID()))
|
||||
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*/)
|
||||
{
|
||||
// Make sure we are not moving a deleted resource.
|
||||
ASSERT( !IsMarkedForDeletion() );
|
||||
|
||||
if (!CanMoveTo(rkDir, rkName)) return false;
|
||||
|
||||
// Store old paths
|
||||
|
@ -487,7 +507,15 @@ bool CResourceEntry::MoveAndRename(const TString& rkDir, const TString& rkName,
|
|||
TString OldMetaPath = MetadataFilePath();
|
||||
|
||||
// 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;
|
||||
|
||||
// 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 (FSMoveSuccess)
|
||||
{
|
||||
if (mpDirectory != pOldDir)
|
||||
if (mpDirectory != pOldDir && pOldDir != nullptr)
|
||||
{
|
||||
FSMoveSuccess = pOldDir->RemoveChildResource(this);
|
||||
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);
|
||||
mpDirectory = pOldDir;
|
||||
mName = OldName;
|
||||
mpStore->ConditionalDeleteDirectory(pNewDir, false);
|
||||
|
||||
if (!DirAlreadyExisted)
|
||||
{
|
||||
mpStore->ConditionalDeleteDirectory(pNewDir, true);
|
||||
}
|
||||
|
||||
if (FileUtil::Exists(NewRawPath))
|
||||
FileUtil::MoveFile(NewRawPath, OldRawPath);
|
||||
|
@ -613,6 +645,96 @@ bool CResourceEntry::Rename(const TString& rkName, bool IsAutoGenName /*= false*
|
|||
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
|
||||
{
|
||||
return mpStore ? mpStore->Project() : nullptr;
|
||||
|
|
|
@ -21,6 +21,7 @@ enum class EResEntryFlag
|
|||
HasBeenModified = 0x00000008, // Resource has been modified and resaved by the user
|
||||
AutoResName = 0x00000010, // Resource 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)
|
||||
|
||||
|
@ -67,7 +68,7 @@ public:
|
|||
bool IsInDirectory(CVirtualDirectory *pDir) const;
|
||||
uint64 Size() const;
|
||||
bool NeedsRecook() const;
|
||||
bool Save(bool SkipCacheSave = false);
|
||||
bool Save(bool SkipCacheSave = false, bool FlagForRecook = true);
|
||||
bool Cook();
|
||||
CResource* Load();
|
||||
CResource* LoadCooked(IInputStream& rInput);
|
||||
|
@ -76,6 +77,7 @@ public:
|
|||
bool MoveAndRename(const TString& rkDir, const TString& rkName, bool IsAutoGenDir = false, bool IsAutoGenName = false);
|
||||
bool Move(const TString& rkDir, bool IsAutoGenDir = false);
|
||||
bool Rename(const TString& rkName, bool IsAutoGenName = false);
|
||||
void MarkDeleted(bool InDeleted);
|
||||
|
||||
CGameProject* Project() const;
|
||||
EGame Game() const;
|
||||
|
@ -87,9 +89,10 @@ public:
|
|||
inline void SetFlagEnabled(EResEntryFlag Flag, bool Enabled) { Enabled ? SetFlag(Flag) : ClearFlag(Flag); }
|
||||
|
||||
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 IsHidden() const { return HasFlag(EResEntryFlag::Hidden); }
|
||||
inline bool IsMarkedForDeletion() const { return HasFlag(EResEntryFlag::MarkedForDeletion); }
|
||||
|
||||
inline bool IsLoaded() const { return mpResource != nullptr; }
|
||||
inline bool IsCategorized() const { return mpDirectory && !mpDirectory->FullPath().CaseInsensitiveCompare( mpStore->DefaultResourceDirPath() ); }
|
||||
|
|
|
@ -21,6 +21,8 @@ public:
|
|||
}
|
||||
|
||||
virtual CResourceEntry* Next()
|
||||
{
|
||||
do
|
||||
{
|
||||
if (mIter != mpkStore->mResourceEntries.end())
|
||||
{
|
||||
|
@ -28,6 +30,8 @@ public:
|
|||
mIter++;
|
||||
}
|
||||
else mpCurEntry = nullptr;
|
||||
}
|
||||
while (mpCurEntry && mpCurEntry->IsMarkedForDeletion());
|
||||
|
||||
return mpCurEntry;
|
||||
}
|
||||
|
|
|
@ -66,6 +66,22 @@ bool CResourceStore::SerializeDatabaseCache(IArchive& rArc)
|
|||
{
|
||||
// Serialize resources
|
||||
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);
|
||||
|
||||
if (rArc.IsReader())
|
||||
|
@ -84,6 +100,8 @@ bool CResourceStore::SerializeDatabaseCache(IArchive& rArc)
|
|||
else
|
||||
{
|
||||
for (CResourceIterator It(this); It; ++It)
|
||||
{
|
||||
if (!It->IsMarkedForDeletion())
|
||||
{
|
||||
if (rArc.ParamBegin("Resource", 0))
|
||||
{
|
||||
|
@ -92,6 +110,7 @@ bool CResourceStore::SerializeDatabaseCache(IArchive& rArc)
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rArc.ParamEnd();
|
||||
}
|
||||
|
||||
|
@ -140,18 +159,22 @@ bool CResourceStore::LoadDatabaseCache()
|
|||
}
|
||||
else
|
||||
{
|
||||
// Database is succesfully loaded at this point
|
||||
// Database is successfully loaded at this point
|
||||
if (mpProj)
|
||||
{
|
||||
ASSERT(mpProj->Game() == Reader.Game());
|
||||
}
|
||||
|
||||
mGame = Reader.Game();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CResourceStore::SaveDatabaseCache()
|
||||
{
|
||||
TString Path = DatabasePath();
|
||||
debugf("Saving database cache...");
|
||||
|
||||
CBasicBinaryWriter Writer(Path, FOURCC('CACH'), 0, mGame);
|
||||
|
||||
|
@ -182,6 +205,14 @@ void CResourceStore::SetProject(CGameProject *pProj)
|
|||
mDatabasePath = mpProj->ProjectRoot();
|
||||
mpDatabaseRoot = new CVirtualDirectory(this);
|
||||
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);
|
||||
}
|
||||
|
||||
// Clear deleted files from previous runs
|
||||
TString DeletedPath = DeletedResourcePath();
|
||||
|
||||
if (FileUtil::Exists(DeletedPath))
|
||||
{
|
||||
FileUtil::ClearDirectory(DeletedPath);
|
||||
}
|
||||
|
||||
delete mpDatabaseRoot;
|
||||
mpDatabaseRoot = nullptr;
|
||||
mpProj = nullptr;
|
||||
|
@ -256,12 +295,27 @@ TString CResourceStore::DefaultResourceDirPath() const
|
|||
return StaticDefaultResourceDirPath( mGame );
|
||||
}
|
||||
|
||||
TString CResourceStore::DeletedResourcePath() const
|
||||
{
|
||||
return mpProj->HiddenFilesDir() / "delete/";
|
||||
}
|
||||
|
||||
CResourceEntry* CResourceStore::FindEntry(const CAssetID& rkID) const
|
||||
{
|
||||
if (!rkID.IsValid()) return nullptr;
|
||||
if (rkID.IsValid())
|
||||
{
|
||||
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
|
||||
|
@ -284,7 +338,14 @@ void CResourceStore::ClearDatabase()
|
|||
{
|
||||
// THIS OPERATION REQUIRES THAT ALL RESOURCES ARE UNREFERENCED
|
||||
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
|
||||
for (auto Iter = mResourceEntries.begin(); Iter != mResourceEntries.end(); Iter++)
|
||||
|
@ -384,7 +445,7 @@ bool CResourceStore::IsResourceRegistered(const CAssetID& rkID) const
|
|||
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);
|
||||
|
||||
|
@ -398,6 +459,14 @@ CResourceEntry* CResourceStore::RegisterResource(const CAssetID& rkID, EResource
|
|||
{
|
||||
pEntry = CResourceEntry::CreateNewResource(this, rkID, rkDir, rkName, Type);
|
||||
mResourceEntries[rkID] = pEntry;
|
||||
mDatabaseCacheDirty = true;
|
||||
|
||||
if (pEntry->IsLoaded())
|
||||
{
|
||||
TrackLoadedResource(pEntry);
|
||||
}
|
||||
|
||||
debugf("CREATED NEW RESOURCE: [%s] %s", *rkID.ToString(), *pEntry->CookedAssetPath());
|
||||
}
|
||||
|
||||
else
|
||||
|
|
|
@ -52,9 +52,10 @@ public:
|
|||
void CreateVirtualDirectory(const TString& rkPath);
|
||||
void ConditionalDeleteDirectory(CVirtualDirectory *pDir, bool Recurse);
|
||||
TString DefaultResourceDirPath() const;
|
||||
TString DeletedResourcePath() 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 TString& rkPath) const;
|
||||
bool AreAllEntriesValid() const;
|
||||
|
|
|
@ -46,6 +46,28 @@ bool CVirtualDirectory::IsDescendantOf(CVirtualDirectory *pDir) const
|
|||
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
|
||||
{
|
||||
if (IsRoot())
|
||||
|
|
|
@ -26,6 +26,7 @@ public:
|
|||
|
||||
bool IsEmpty(bool CheckFilesystem) const;
|
||||
bool IsDescendantOf(CVirtualDirectory *pDir) const;
|
||||
bool IsSafeToDelete() const;
|
||||
TString FullPath() const;
|
||||
TString AbsolutePath() const;
|
||||
CVirtualDirectory* GetRoot();
|
||||
|
|
|
@ -113,6 +113,7 @@ void CCharacterUsageMap::DebugPrintContents()
|
|||
// ************ PROTECTED ************
|
||||
void CCharacterUsageMap::ParseDependencyNode(IDependencyNode *pNode)
|
||||
{
|
||||
if (!pNode) return;
|
||||
EDependencyNodeType Type = pNode->Type();
|
||||
|
||||
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)
|
||||
{
|
||||
if (!pNode) return;
|
||||
EDependencyNodeType Type = pNode->Type();
|
||||
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)
|
||||
{
|
||||
if (!pNode) return;
|
||||
EDependencyNodeType Type = pNode->Type();
|
||||
bool ParseChildren = false;
|
||||
|
||||
|
@ -556,3 +559,82 @@ void CAreaDependencyListBuilder::EvaluateDependencyNode(CResourceEntry *pCurEntr
|
|||
EvaluateDependencyNode(pCurEntry, pNode->ChildByIndex(iChild), rOut, pAudioGroupsOut);
|
||||
}
|
||||
}
|
||||
|
||||
// ************ CAssetDependencyListBuilder ************
|
||||
void CAssetDependencyListBuilder::BuildDependencyList(std::vector<CAssetID>& OutAssets)
|
||||
{
|
||||
mCharacterUsageMap.FindUsagesForAsset(mpResourceEntry);
|
||||
EvaluateDependencyNode(mpResourceEntry, mpResourceEntry->Dependencies(), OutAssets);
|
||||
}
|
||||
|
||||
void CAssetDependencyListBuilder::AddDependency(const CAssetID& kID, std::vector<CAssetID>& Out)
|
||||
{
|
||||
CResourceEntry *pEntry = mpResourceEntry->ResourceStore()->FindEntry(kID);
|
||||
if (!pEntry) return;
|
||||
|
||||
EResourceType ResType = pEntry->ResourceType();
|
||||
|
||||
if (mUsedAssets.find(kID) != mUsedAssets.end())
|
||||
return;
|
||||
|
||||
// Dependency is valid! Evaluate the node tree
|
||||
if (ResType == EResourceType::AnimSet)
|
||||
{
|
||||
ASSERT(!mCurrentAnimSetID.IsValid());
|
||||
mCurrentAnimSetID = pEntry->ID();
|
||||
}
|
||||
|
||||
EvaluateDependencyNode(pEntry, pEntry->Dependencies(), Out);
|
||||
|
||||
if (ResType == EResourceType::AnimSet)
|
||||
{
|
||||
ASSERT(mCurrentAnimSetID.IsValid());
|
||||
mCurrentAnimSetID = CAssetID::InvalidID(mpResourceEntry->Game());
|
||||
}
|
||||
|
||||
Out.push_back(kID);
|
||||
mUsedAssets.insert(kID);
|
||||
}
|
||||
|
||||
void CAssetDependencyListBuilder::EvaluateDependencyNode(CResourceEntry* pCurEntry, IDependencyNode* pNode, std::vector<CAssetID>& Out)
|
||||
{
|
||||
if (!pNode) return;
|
||||
EDependencyNodeType Type = pNode->Type();
|
||||
bool ParseChildren = false;
|
||||
|
||||
if (Type == EDependencyNodeType::Resource || Type == EDependencyNodeType::ScriptProperty || Type == EDependencyNodeType::CharacterProperty)
|
||||
{
|
||||
CResourceDependency* pDep = static_cast<CResourceDependency*>(pNode);
|
||||
AddDependency(pDep->ID(), Out);
|
||||
}
|
||||
|
||||
else if (Type == EDependencyNodeType::AnimEvent)
|
||||
{
|
||||
CAnimEventDependency* pDep = static_cast<CAnimEventDependency*>(pNode);
|
||||
uint32 CharIndex = pDep->CharIndex();
|
||||
|
||||
if (CharIndex == -1 || mCharacterUsageMap.IsCharacterUsed(mCurrentAnimSetID, CharIndex))
|
||||
AddDependency(pDep->ID(), Out);
|
||||
}
|
||||
|
||||
else if (Type == EDependencyNodeType::SetCharacter)
|
||||
{
|
||||
CSetCharacterDependency* pChar = static_cast<CSetCharacterDependency*>(pNode);
|
||||
ParseChildren = mCharacterUsageMap.IsCharacterUsed(mCurrentAnimSetID, pChar->CharSetIndex());
|
||||
}
|
||||
|
||||
else if (Type == EDependencyNodeType::SetAnimation)
|
||||
{
|
||||
CSetAnimationDependency* pAnim = static_cast<CSetAnimationDependency*>(pNode);
|
||||
ParseChildren = mCharacterUsageMap.IsAnimationUsed(mCurrentAnimSetID, pAnim);
|
||||
}
|
||||
|
||||
else
|
||||
ParseChildren = true;
|
||||
|
||||
if (ParseChildren)
|
||||
{
|
||||
for (uint32 iChild = 0; iChild < pNode->NumChildren(); iChild++)
|
||||
EvaluateDependencyNode(pCurEntry, pNode->ChildByIndex(iChild), Out);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,5 +96,25 @@ public:
|
|||
void EvaluateDependencyNode(CResourceEntry *pCurEntry, IDependencyNode *pNode, std::list<CAssetID>& rOut, std::set<CAssetID> *pAudioGroupsOut);
|
||||
};
|
||||
|
||||
// ************ CAssetDependencyListBuilder ************
|
||||
//@todo merge with CAreaDependencyListBuilder; code is very similar
|
||||
class CAssetDependencyListBuilder
|
||||
{
|
||||
CResourceEntry* mpResourceEntry;
|
||||
CCharacterUsageMap mCharacterUsageMap;
|
||||
std::set<CAssetID> mUsedAssets;
|
||||
CAssetID mCurrentAnimSetID;
|
||||
|
||||
public:
|
||||
CAssetDependencyListBuilder(CResourceEntry* pEntry)
|
||||
: mpResourceEntry(pEntry)
|
||||
, mCharacterUsageMap(pEntry->ResourceStore())
|
||||
{}
|
||||
|
||||
void BuildDependencyList(std::vector<CAssetID>& OutAssets);
|
||||
void AddDependency(const CAssetID& kID, std::vector<CAssetID>& Out);
|
||||
void EvaluateDependencyNode(CResourceEntry* pCurEntry, IDependencyNode* pNode, std::vector<CAssetID>& Out);
|
||||
};
|
||||
|
||||
#endif // DEPENDENCYLISTBUILDERS
|
||||
|
||||
|
|
|
@ -6,8 +6,10 @@
|
|||
class IUIRelay
|
||||
{
|
||||
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 OpenProject(const TString& kPath = "") = 0;
|
||||
};
|
||||
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);
|
||||
rSCLY.WriteLong(0);
|
||||
rSCLY.WriteLong(0xFFFFFFFF);
|
||||
rSCLY.WriteLong(mGame >= EGame::EchoesDemo ? 0 : 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
#define CANIMATIONPARAMETERS_H
|
||||
|
||||
#include "Core/Resource/TResPtr.h"
|
||||
#include "Core/Resource/Model/CModel.h"
|
||||
#include <Common/EGame.h>
|
||||
|
||||
class CModel;
|
||||
|
||||
class CAnimationParameters
|
||||
{
|
||||
EGame mGame;
|
||||
|
|
|
@ -10,6 +10,7 @@ CResTypeInfo::CResTypeInfo(EResourceType Type, const TString& rkTypeName, const
|
|||
, mRetroExtension(rkRetroExtension)
|
||||
, mCanBeSerialized(false)
|
||||
, mCanHaveDependencies(true)
|
||||
, mCanBeCreated(false)
|
||||
{
|
||||
#if !PUBLIC_RELEASE
|
||||
ASSERT(smTypeMap.find(Type) == smTypeMap.end());
|
||||
|
@ -195,7 +196,7 @@ void CResTypeInfo::CResTypeInfoFactory::InitTypes()
|
|||
}
|
||||
{
|
||||
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;
|
||||
}
|
||||
{
|
||||
|
@ -332,6 +333,7 @@ void CResTypeInfo::CResTypeInfoFactory::InitTypes()
|
|||
{
|
||||
CResTypeInfo *pType = new CResTypeInfo(EResourceType::Scan, "Scan", "scan");
|
||||
AddExtension(pType, "SCAN", EGame::PrimeDemo, EGame::Corruption);
|
||||
pType->mCanBeCreated = true;
|
||||
}
|
||||
{
|
||||
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");
|
||||
AddExtension(pType, "STRG", EGame::PrimeDemo, EGame::DKCReturns);
|
||||
pType->mCanBeSerialized = true;
|
||||
pType->mCanBeCreated = true;
|
||||
}
|
||||
{
|
||||
CResTypeInfo *pType = new CResTypeInfo(EResourceType::Texture, "Texture", "txtr");
|
||||
|
@ -388,7 +392,7 @@ void CResTypeInfo::CResTypeInfoFactory::InitTypes()
|
|||
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);
|
||||
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
|
||||
bool mCanBeSerialized;
|
||||
bool mCanHaveDependencies;
|
||||
bool mCanBeCreated;
|
||||
|
||||
static std::unordered_map<EResourceType, CResTypeInfo*> smTypeMap;
|
||||
|
||||
|
@ -40,6 +41,7 @@ public:
|
|||
inline TString TypeName() const { return mTypeName; }
|
||||
inline bool CanBeSerialized() const { return mCanBeSerialized; }
|
||||
inline bool CanHaveDependencies() const { return mCanHaveDependencies; }
|
||||
inline bool CanBeCreated() const { return mCanBeCreated; }
|
||||
|
||||
// Static
|
||||
static void GetAllTypesInGame(EGame Game, std::list<CResTypeInfo*>& rOut);
|
||||
|
|
|
@ -43,6 +43,7 @@ public:
|
|||
virtual ~CResource() {}
|
||||
virtual CDependencyTree* BuildDependencyTree() const { return new CDependencyTree(); }
|
||||
virtual void Serialize(IArchive& /*rArc*/) {}
|
||||
virtual void InitializeNewResource() {}
|
||||
|
||||
inline CResourceEntry* Entry() const { return mpEntry; }
|
||||
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
|
||||
{
|
||||
if (mpWorldName)
|
||||
return mpWorldName->String("ENGL", 0);
|
||||
return mpWorldName->GetString(ELanguage::English, 0);
|
||||
else
|
||||
return Entry()->Name();
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ TString CWorld::AreaInGameName(uint32 AreaIndex) const
|
|||
const SArea& rkArea = mAreas[AreaIndex];
|
||||
|
||||
if (rkArea.pAreaName)
|
||||
return rkArea.pAreaName->String("ENGL", 0);
|
||||
return rkArea.pAreaName->GetString(ELanguage::English, 0);
|
||||
else
|
||||
return "!!" + rkArea.InternalName;
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
|
||||
#include "CResource.h"
|
||||
#include "CSavedStateID.h"
|
||||
#include "CStringTable.h"
|
||||
#include "Core/Resource/Area/CGameArea.h"
|
||||
#include "Core/Resource/Model/CModel.h"
|
||||
#include "Core/Resource/StringTable/CStringTable.h"
|
||||
#include <Common/Math/CTransform4f.h>
|
||||
|
||||
class CWorld : public CResource
|
||||
|
|
|
@ -4,8 +4,12 @@
|
|||
#include "CAreaCooker.h"
|
||||
#include "CModelCooker.h"
|
||||
#include "CPoiToWorldCooker.h"
|
||||
#include "CScanCooker.h"
|
||||
#include "CStringCooker.h"
|
||||
#include "CWorldCooker.h"
|
||||
|
||||
#include "Core/Tweaks/CTweakCooker.h"
|
||||
|
||||
#include "Core/GameProject/CResourceEntry.h"
|
||||
|
||||
class CResourceCooker
|
||||
|
@ -22,7 +26,10 @@ public:
|
|||
{
|
||||
case EResourceType::Area: return CAreaCooker::CookMREA((CGameArea*) pRes, rOutput);
|
||||
case EResourceType::Model: return CModelCooker::CookCMDL((CModel*) pRes, rOutput);
|
||||
case EResourceType::Scan: return CScanCooker::CookSCAN((CScan*) pRes, rOutput);
|
||||
case EResourceType::StaticGeometryMap: return CPoiToWorldCooker::CookEGMC((CPoiToWorld*) pRes, rOutput);
|
||||
case EResourceType::StringTable: return CStringCooker::CookSTRG((CStringTable*) pRes, rOutput);
|
||||
case EResourceType::Tweaks: return CTweakCooker::CookCTWK((CTweakData*) pRes, rOutput);
|
||||
case EResourceType::World: return CWorldCooker::CookMLVL((CWorld*) pRes, rOutput);
|
||||
|
||||
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/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;
|
||||
void* pData = (mpArrayItemData ? mpArrayItemData : mpObject->PropertyData());
|
||||
|
||||
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++)
|
||||
WriteProperty(rOut, PropertiesToWrite[PropertyIdx], pStruct->IsAtomic());
|
||||
WriteProperty(rOut, PropertiesToWrite[PropertyIdx], pData, pStruct->IsAtomic());
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -208,15 +207,11 @@ void CScriptCooker::WriteProperty(IOutputStream& rOut, IProperty* pProperty, boo
|
|||
uint32 Count = pArray->ArrayCount(pData);
|
||||
rOut.WriteLong(Count);
|
||||
|
||||
void* pOldItemData = mpArrayItemData;
|
||||
|
||||
for (uint32 ElementIdx = 0; ElementIdx < pArray->ArrayCount(pData); ElementIdx++)
|
||||
{
|
||||
mpArrayItemData = pArray->ItemPointer(pData, ElementIdx);
|
||||
WriteProperty(rOut, pArray->ItemArchetype(), true);
|
||||
WriteProperty(rOut, pArray->ItemArchetype(), pArray->ItemPointer(pData, ElementIdx), true);
|
||||
}
|
||||
|
||||
mpArrayItemData = pOldItemData;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -231,7 +226,6 @@ void CScriptCooker::WriteProperty(IOutputStream& rOut, IProperty* pProperty, boo
|
|||
}
|
||||
}
|
||||
|
||||
// ************ PUBLIC ************
|
||||
void CScriptCooker::WriteInstance(IOutputStream& rOut, CScriptObject *pInstance)
|
||||
{
|
||||
ASSERT(pInstance->Area()->Game() == mGame);
|
||||
|
@ -261,8 +255,7 @@ void CScriptCooker::WriteInstance(IOutputStream& rOut, CScriptObject *pInstance)
|
|||
rOut.WriteLong(pLink->ReceiverID());
|
||||
}
|
||||
|
||||
mpObject = pInstance;
|
||||
WriteProperty(rOut, pInstance->Template()->Properties(), false);
|
||||
WriteProperty(rOut, pInstance->Template()->Properties(), pInstance->PropertyData(), false);
|
||||
uint32 InstanceEnd = rOut.Tell();
|
||||
|
||||
rOut.Seek(SizeOffset, SEEK_SET);
|
||||
|
|
|
@ -10,21 +10,16 @@
|
|||
class CScriptCooker
|
||||
{
|
||||
EGame mGame;
|
||||
CScriptObject* mpObject;
|
||||
void* mpArrayItemData;
|
||||
std::vector<CScriptObject*> mGeneratedObjects;
|
||||
bool mWriteGeneratedSeparately;
|
||||
|
||||
void WriteProperty(IOutputStream& rOut, IProperty* pProperty, bool InAtomicStruct);
|
||||
|
||||
public:
|
||||
CScriptCooker(EGame Game, bool WriteGeneratedObjectsSeparately = true)
|
||||
: mGame(Game)
|
||||
, mpObject(nullptr)
|
||||
, mpArrayItemData(nullptr)
|
||||
, mWriteGeneratedSeparately(WriteGeneratedObjectsSeparately && mGame >= EGame::EchoesDemo)
|
||||
{}
|
||||
|
||||
void WriteProperty(IOutputStream& rOut, IProperty* pProperty, void* pData, bool InAtomicStruct);
|
||||
void WriteInstance(IOutputStream& rOut, CScriptObject *pInstance);
|
||||
void WriteLayer(IOutputStream& rOut, CScriptLayer *pLayer);
|
||||
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,
|
||||
StringTable,
|
||||
Texture,
|
||||
Tweak,
|
||||
Tweaks,
|
||||
UserEvaluatorData,
|
||||
Video,
|
||||
World,
|
||||
|
|
|
@ -81,7 +81,7 @@ void CAnimationLoader::ReadUncompressedANIM()
|
|||
// Echoes only - rotation channel indices
|
||||
std::vector<uint8> RotationIndices;
|
||||
|
||||
if (mGame == EGame::Echoes)
|
||||
if (mGame >= EGame::EchoesDemo)
|
||||
{
|
||||
uint32 NumRotationIndices = mpInput->ReadLong();
|
||||
RotationIndices.resize(NumRotationIndices);
|
||||
|
@ -125,7 +125,7 @@ void CAnimationLoader::ReadUncompressedANIM()
|
|||
// Echoes only - scale channel indices
|
||||
std::vector<uint8> ScaleIndices;
|
||||
|
||||
if (mGame == EGame::Echoes)
|
||||
if (mGame >= EGame::EchoesDemo)
|
||||
{
|
||||
uint32 NumScaleIndices = mpInput->ReadLong();
|
||||
ScaleIndices.resize(NumScaleIndices);
|
||||
|
@ -161,7 +161,7 @@ void CAnimationLoader::ReadUncompressedANIM()
|
|||
}
|
||||
|
||||
// Read bone transforms
|
||||
if (mGame == EGame::Echoes)
|
||||
if (mGame >= EGame::EchoesDemo)
|
||||
{
|
||||
mpInput->Seek(0x4, SEEK_CUR); // Skipping scale key count
|
||||
mpAnim->mScaleChannels.resize(NumScaleChannels);
|
||||
|
@ -208,23 +208,24 @@ void CAnimationLoader::ReadCompressedANIM()
|
|||
// Header
|
||||
mpInput->Seek(0x4, SEEK_CUR); // Skip alloc size
|
||||
|
||||
if (mGame == EGame::Invalid)
|
||||
// Version check
|
||||
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());
|
||||
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->mTickInterval = mpInput->ReadFloat();
|
||||
mpInput->Seek(0x8, SEEK_CUR); // Skip two unknown values
|
||||
|
||||
mRotationDivisor = mpInput->ReadLong();
|
||||
mTranslationMultiplier = mpInput->ReadFloat();
|
||||
if (mGame == EGame::Echoes) mScaleMultiplier = mpInput->ReadFloat();
|
||||
if (mGame >= EGame::EchoesDemo) mScaleMultiplier = mpInput->ReadFloat();
|
||||
uint32 NumBoneChannels = mpInput->ReadLong();
|
||||
mpInput->Seek(0x4, SEEK_CUR); // Skip unknown value
|
||||
|
||||
|
@ -284,7 +285,7 @@ void CAnimationLoader::ReadCompressedANIM()
|
|||
// Read scale parameters
|
||||
uint8 ScaleIdx = 0xFF;
|
||||
|
||||
if (mGame == EGame::Echoes)
|
||||
if (mGame >= EGame::EchoesDemo)
|
||||
{
|
||||
rChan.NumScaleKeys = mpInput->ReadShort();
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
#include "CUnsupportedParticleLoader.h"
|
||||
#include "CWorldLoader.h"
|
||||
|
||||
#include "Core/Tweaks/CTweakLoader.h"
|
||||
|
||||
#include "Core/Resource/Resources.h"
|
||||
|
||||
// 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::StringTable: pRes = CStringLoader::LoadSTRG(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::StateMachine:
|
||||
|
|
|
@ -1,306 +1,83 @@
|
|||
#include "CScanLoader.h"
|
||||
#include "Core/GameProject/CResourceStore.h"
|
||||
#include "CScriptLoader.h"
|
||||
#include <Common/Log.h>
|
||||
|
||||
CScanLoader::CScanLoader()
|
||||
CScan* CScanLoader::LoadScanMP1(IInputStream& SCAN, CResourceEntry* pEntry)
|
||||
{
|
||||
}
|
||||
|
||||
CScan* CScanLoader::LoadScanMP1(IInputStream& rSCAN)
|
||||
{
|
||||
// Basic support at the moment - don't read animation/scan image data
|
||||
mpScan->mFrameID = CAssetID(rSCAN, k32Bit);
|
||||
mpScan->mpStringTable = gpResourceStore->LoadResource(rSCAN.ReadLong(), EResourceType::StringTable);
|
||||
mpScan->mIsSlow = (rSCAN.ReadLong() != 0);
|
||||
mpScan->mCategory = (CScan::ELogbookCategory) rSCAN.ReadLong();
|
||||
mpScan->mIsImportant = (rSCAN.ReadByte() == 1);
|
||||
|
||||
for (uint32 iImg = 0; iImg < 4; iImg++)
|
||||
{
|
||||
mpScan->mScanImageTextures[iImg] = CAssetID(rSCAN, k32Bit);
|
||||
rSCAN.Seek(0x18, SEEK_CUR);
|
||||
}
|
||||
|
||||
return mpScan;
|
||||
}
|
||||
|
||||
CScan* CScanLoader::LoadScanMP2(IInputStream& rSCAN)
|
||||
{
|
||||
// The SCAN format in MP2 embeds a SNFO object using the same format as SCLY
|
||||
// However since the contents of the file are consistent there's no need to delegate to CScriptLoader
|
||||
rSCAN.Skip(0x1);
|
||||
uint32 NumInstances = rSCAN.ReadLong();
|
||||
|
||||
if (NumInstances != 1)
|
||||
{
|
||||
errorf("%s: SCAN has multiple instances", *rSCAN.GetSourceString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32 ScanInfoStart = rSCAN.Tell();
|
||||
|
||||
CFourCC SNFO(rSCAN);
|
||||
if (SNFO != FOURCC('SNFO'))
|
||||
{
|
||||
errorf("%s [0x%X]: Unrecognized SCAN object type: %s", *rSCAN.GetSourceString(), ScanInfoStart, *SNFO.ToString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint16 InstanceSize = rSCAN.ReadShort();
|
||||
uint32 InstanceEnd = rSCAN.Tell() + InstanceSize;
|
||||
rSCAN.Skip(0x4);
|
||||
|
||||
uint16 NumConnections = rSCAN.ReadShort();
|
||||
if (NumConnections > 0)
|
||||
{
|
||||
warnf("%s [0x%X]: SNFO object in SCAN has connections", *rSCAN.GetSourceString(), ScanInfoStart);
|
||||
rSCAN.Skip(NumConnections * 0xC);
|
||||
}
|
||||
|
||||
uint32 BasePropID = rSCAN.ReadLong();
|
||||
if (BasePropID != 0xFFFFFFFF)
|
||||
{
|
||||
errorf("%s [0x%X]: Invalid base property ID: 0x%08X", *rSCAN.GetSourceString(), rSCAN.Tell() - 4, BasePropID);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
rSCAN.Skip(0x2);
|
||||
uint16 NumProperties = rSCAN.ReadShort();
|
||||
|
||||
switch (NumProperties)
|
||||
{
|
||||
case 0x14:
|
||||
case 0xB:
|
||||
mpScan = new CScan(mpEntry);
|
||||
LoadParamsMP2(rSCAN, NumProperties);
|
||||
break;
|
||||
case 0x12:
|
||||
case 0x15:
|
||||
case 0x16:
|
||||
mpScan = new CScan(mpEntry);
|
||||
LoadParamsMP3(rSCAN, NumProperties);
|
||||
break;
|
||||
default:
|
||||
errorf("%s [0x%X]: Invalid SNFO property count: 0x%X", *rSCAN.GetSourceString(), rSCAN.Tell() - 2, NumProperties);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Load MP3 dependency list
|
||||
if (mpScan->Game() == EGame::Corruption)
|
||||
{
|
||||
rSCAN.GoTo(InstanceEnd);
|
||||
uint32 NumDeps = rSCAN.ReadLong();
|
||||
|
||||
for (uint32 DepIdx = 0; DepIdx < NumDeps; DepIdx++)
|
||||
{
|
||||
rSCAN.Skip(4);
|
||||
CAssetID ID(rSCAN, mpScan->Game());
|
||||
mpScan->mDependencyList.push_back(ID);
|
||||
}
|
||||
}
|
||||
|
||||
return mpScan;
|
||||
}
|
||||
|
||||
void CScanLoader::LoadParamsMP2(IInputStream& rSCAN, uint16 NumProperties)
|
||||
{
|
||||
// Function begins after the SNFO property count
|
||||
mpScan->mSecondaryModels.resize(9);
|
||||
|
||||
for (uint32 iProp = 0; iProp < NumProperties; iProp++)
|
||||
{
|
||||
uint32 PropertyID = rSCAN.ReadLong();
|
||||
uint16 PropertySize = rSCAN.ReadShort();
|
||||
uint32 Next = rSCAN.Tell() + PropertySize;
|
||||
|
||||
switch (PropertyID)
|
||||
{
|
||||
case 0x2F5B6423:
|
||||
mpScan->mpStringTable = gpResourceStore->LoadResource(rSCAN.ReadLong(), EResourceType::StringTable);
|
||||
break;
|
||||
|
||||
case 0xC308A322:
|
||||
mpScan->mIsSlow = (rSCAN.ReadLong() != 0);
|
||||
break;
|
||||
|
||||
case 0x7B714814:
|
||||
mpScan->mIsImportant = rSCAN.ReadBool();
|
||||
break;
|
||||
|
||||
case 0x1733B1EC:
|
||||
mpScan->mUseLogbookModelPostScan = rSCAN.ReadBool();
|
||||
break;
|
||||
|
||||
case 0x53336141:
|
||||
mpScan->mPostOverrideTexture = CAssetID(rSCAN, mVersion);
|
||||
break;
|
||||
|
||||
case 0x3DE0BA64:
|
||||
mpScan->mLogbookDefaultRotX = rSCAN.ReadFloat();
|
||||
break;
|
||||
|
||||
case 0x2ADD6628:
|
||||
mpScan->mLogbookDefaultRotZ = rSCAN.ReadFloat();
|
||||
break;
|
||||
|
||||
case 0xD0C15066:
|
||||
mpScan->mLogbookScale = rSCAN.ReadFloat();
|
||||
break;
|
||||
|
||||
case 0xB7ADC418:
|
||||
mpScan->mLogbookModel = CAssetID(rSCAN, mVersion);
|
||||
break;
|
||||
|
||||
case 0x15694EE1:
|
||||
mpScan->mLogbookAnimParams = CAnimationParameters(rSCAN, mVersion);
|
||||
break;
|
||||
|
||||
case 0x58F9FE99:
|
||||
mpScan->mUnknownAnimParams = CAnimationParameters(rSCAN, mVersion);
|
||||
break;
|
||||
|
||||
case 0x1C5B4A3A:
|
||||
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[0] );
|
||||
break;
|
||||
|
||||
case 0x8728A0EE:
|
||||
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[1] );
|
||||
break;
|
||||
|
||||
case 0xF1CD99D3:
|
||||
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[2] );
|
||||
break;
|
||||
|
||||
case 0x6ABE7307:
|
||||
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[3] );
|
||||
break;
|
||||
|
||||
case 0x1C07EBA9:
|
||||
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[4] );
|
||||
break;
|
||||
|
||||
case 0x8774017D:
|
||||
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[5] );
|
||||
break;
|
||||
|
||||
case 0xF1913840:
|
||||
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[6] );
|
||||
break;
|
||||
|
||||
case 0x6AE2D294:
|
||||
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[7] );
|
||||
break;
|
||||
|
||||
case 0x1CE2091C:
|
||||
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[8] );
|
||||
break;
|
||||
}
|
||||
|
||||
rSCAN.GoTo(Next);
|
||||
}
|
||||
|
||||
mpScan->mCategory = CScan::ELogbookCategory::None;
|
||||
}
|
||||
|
||||
void CScanLoader::LoadParamsMP3(IInputStream& rSCAN, uint16 NumProperties)
|
||||
{
|
||||
// Function begins after the SNFO property count
|
||||
for (uint32 iProp = 0; iProp < NumProperties; iProp++)
|
||||
{
|
||||
uint32 PropertyID = rSCAN.ReadLong();
|
||||
uint16 PropertySize = rSCAN.ReadShort();
|
||||
uint32 Next = rSCAN.Tell() + PropertySize;
|
||||
|
||||
switch (PropertyID)
|
||||
{
|
||||
case 0x2F5B6423:
|
||||
mpScan->mpStringTable = gpResourceStore->LoadResource(rSCAN.ReadLongLong(), EResourceType::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);
|
||||
}
|
||||
// Validate magic
|
||||
uint Magic = SCAN.ReadLong();
|
||||
|
||||
if (Magic != 0x0BADBEEF)
|
||||
{
|
||||
errorf("%s: Invalid SCAN magic: 0x%08X", *rSCAN.GetSourceString(), Magic);
|
||||
errorf("Invalid magic in SCAN asset: 0x%08X", Magic);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (FileVersion != 5)
|
||||
// The SCAN format in MP2 and later games uses the script loader to load SCAN parameters.
|
||||
// The MP1 format is not loaded the same way, as far as I'm aware, and is loaded the same
|
||||
// way as a normal file format... however, since we support all games, we need to support
|
||||
// the script object method for proper MP2/3 support (including dealing with property names/IDs).
|
||||
// So, it's simplest to use the script loader to load the MP1 SCAN format as well... that enables
|
||||
// us to just create one class for all SCAN assets that works for every game.
|
||||
mpScan = new CScan(pEntry);
|
||||
CScriptLoader::LoadStructData(SCAN, mpScan->ScanData());
|
||||
return mpScan;
|
||||
}
|
||||
|
||||
CScan* CScanLoader::LoadScanMP2(IInputStream& SCAN, CResourceEntry* pEntry)
|
||||
{
|
||||
errorf("%s: Unsupported SCAN version: 0x%X", *rSCAN.GetSourceString(), FileVersion);
|
||||
// Validate version
|
||||
uint Version = SCAN.ReadLong();
|
||||
|
||||
if (Version != 2)
|
||||
{
|
||||
errorf("Unrecognized SCAN version: %d", Version);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// MP1 SCAN - read the file!
|
||||
CScanLoader Loader;
|
||||
Loader.mVersion = EGame::Prime;
|
||||
Loader.mpScan = new CScan(pEntry);
|
||||
Loader.mpEntry = pEntry;
|
||||
return Loader.LoadScanMP1(rSCAN);
|
||||
// The SCAN format in MP2 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
|
||||
#define CSCANLOADER_H
|
||||
|
||||
#include "Core/Resource/CScan.h"
|
||||
#include "Core/Resource/Scan/CScan.h"
|
||||
#include <Common/EGame.h>
|
||||
|
||||
class CScanLoader
|
||||
{
|
||||
TResPtr<CScan> mpScan;
|
||||
CResourceEntry *mpEntry;
|
||||
EGame mVersion;
|
||||
|
||||
CScanLoader();
|
||||
CScan* LoadScanMP1(IInputStream& rSCAN);
|
||||
CScan* LoadScanMP2(IInputStream& rSCAN);
|
||||
void LoadParamsMP2(IInputStream& rSCAN, uint16 NumProperties);
|
||||
void LoadParamsMP3(IInputStream& rSCAN, uint16 NumProperties);
|
||||
void LoadScanInfoSecondaryModel(IInputStream& rSCAN, CScan::SScanInfoSecondaryModel& rSecondaryModel);
|
||||
CScanLoader() {}
|
||||
CScan* LoadScanMP1(IInputStream& SCAN, CResourceEntry* pEntry);
|
||||
CScan* LoadScanMP2(IInputStream& SCAN, CResourceEntry* pEntry);
|
||||
|
||||
public:
|
||||
static CScan* LoadSCAN(IInputStream& rSCAN, CResourceEntry *pEntry);
|
||||
|
|
|
@ -15,13 +15,13 @@
|
|||
|
||||
CScriptLoader::CScriptLoader()
|
||||
: mpObj(nullptr)
|
||||
, mpArrayItemData(nullptr)
|
||||
, mpCurrentData(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
|
@ -151,7 +151,7 @@ void CScriptLoader::ReadProperty(IProperty *pProp, uint32 Size, IInputStream& rS
|
|||
#if VALIDATE_PROPERTY_VALUES
|
||||
CAssetID ID = pAsset->ValueRef(pData);
|
||||
|
||||
if (ID.IsValid())
|
||||
if (ID.IsValid() && gpResourceStore)
|
||||
{
|
||||
CResourceEntry *pEntry = gpResourceStore->FindEntry(ID);
|
||||
|
||||
|
@ -237,7 +237,7 @@ void CScriptLoader::ReadProperty(IProperty *pProp, uint32 Size, IInputStream& rS
|
|||
int Count = rSCLY.ReadLong();
|
||||
|
||||
pArray->Resize(pData, Count);
|
||||
void* pOldArrayItemData = mpArrayItemData;
|
||||
void* pOldData = mpCurrentData;
|
||||
|
||||
// Make sure the array archetype is atomic... non-atomic array archetypes is not supported
|
||||
// because arrays can only have one possible archetype so having property IDs here wouldn't make sense
|
||||
|
@ -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
|
||||
* things to make this cleaner
|
||||
*/
|
||||
mpArrayItemData = pArray->ItemPointer(pData, ElementIdx);
|
||||
mpCurrentData = pArray->ItemPointer(pData, ElementIdx);
|
||||
ReadProperty(pArray->ItemArchetype(), 0, rSCLY);
|
||||
}
|
||||
|
||||
mpArrayItemData = pOldArrayItemData;
|
||||
mpCurrentData = pOldData;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -505,3 +505,20 @@ CScriptObject* CScriptLoader::LoadInstance(IInputStream& rSCLY, CGameArea *pArea
|
|||
else
|
||||
return Loader.LoadObjectMP2(rSCLY);
|
||||
}
|
||||
|
||||
void CScriptLoader::LoadStructData(IInputStream& rInput, CStructRef InStruct)
|
||||
{
|
||||
if (!rInput.IsValid()) return;
|
||||
|
||||
CScriptLoader Loader;
|
||||
Loader.mVersion = InStruct.Property()->Game();
|
||||
Loader.mpGameTemplate = NGameList::GetGameTemplate(Loader.mVersion);
|
||||
Loader.mpArea = nullptr;
|
||||
Loader.mpLayer = nullptr;
|
||||
Loader.mpCurrentData = InStruct.DataPointer();
|
||||
|
||||
if (Loader.mVersion <= EGame::Prime)
|
||||
Loader.LoadStructMP1(rInput, InStruct.Property());
|
||||
else
|
||||
Loader.LoadStructMP2(rInput, InStruct.Property());
|
||||
}
|
||||
|
|
|
@ -15,8 +15,8 @@ class CScriptLoader
|
|||
CGameArea* mpArea;
|
||||
CGameTemplate *mpGameTemplate;
|
||||
|
||||
// Current array item pointer
|
||||
void* mpArrayItemData;
|
||||
// Current data pointer
|
||||
void* mpCurrentData;
|
||||
|
||||
CScriptLoader();
|
||||
void ReadProperty(IProperty* pProp, uint32 Size, IInputStream& rSCLY);
|
||||
|
@ -32,6 +32,7 @@ class CScriptLoader
|
|||
public:
|
||||
static CScriptLayer* LoadLayer(IInputStream& rSCLY, CGameArea *pArea, EGame Version);
|
||||
static CScriptObject* LoadInstance(IInputStream& rSCLY, CGameArea *pArea, CScriptLayer *pLayer, EGame Version, bool ForceReturnsFormat);
|
||||
static void LoadStructData(IInputStream& rInput, CStructRef InStruct);
|
||||
};
|
||||
|
||||
#endif // CSCRIPTLOADER_H
|
||||
|
|
|
@ -1,193 +1,234 @@
|
|||
#include "CStringLoader.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 STRG version only supports one language per file
|
||||
mpStringTable->mLangTables.resize(1);
|
||||
CStringTable::SLangTable* Lang = &mpStringTable->mLangTables[1];
|
||||
Lang->Language = "ENGL";
|
||||
uint32 TableStart = rSTRG.Tell();
|
||||
mpStringTable->mLanguages.resize(1);
|
||||
CStringTable::SLanguageData& Language = mpStringTable->mLanguages[0];
|
||||
Language.Language = ELanguage::English;
|
||||
uint TableStart = STRG.Tell();
|
||||
|
||||
// Header
|
||||
uint32 NumStrings = rSTRG.ReadLong();
|
||||
Lang->Strings.resize(NumStrings);
|
||||
mpStringTable->mNumStrings = NumStrings;
|
||||
uint NumStrings = STRG.ReadLong();
|
||||
Language.Strings.resize(NumStrings);
|
||||
|
||||
// String offsets (yeah, that wasn't much of a header)
|
||||
std::vector<uint32> StringOffsets(NumStrings);
|
||||
for (uint32 iOff = 0; iOff < StringOffsets.size(); iOff++)
|
||||
StringOffsets[iOff] = rSTRG.ReadLong();
|
||||
std::vector<uint> StringOffsets(NumStrings);
|
||||
for (uint32 OffsetIdx = 0; OffsetIdx < NumStrings; OffsetIdx++)
|
||||
StringOffsets[OffsetIdx] = STRG.ReadLong();
|
||||
|
||||
// Strings
|
||||
for (uint32 iStr = 0; iStr < NumStrings; iStr++)
|
||||
for (uint StringIdx = 0; StringIdx < NumStrings; StringIdx++)
|
||||
{
|
||||
rSTRG.Seek(TableStart + StringOffsets[iStr], SEEK_SET);
|
||||
Lang->Strings[iStr] = rSTRG.ReadWString().ToUTF8();
|
||||
STRG.GoTo( TableStart + StringOffsets[StringIdx] );
|
||||
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
|
||||
// Header
|
||||
uint32 NumLanguages = rSTRG.ReadLong();
|
||||
uint32 NumStrings = rSTRG.ReadLong();
|
||||
mpStringTable->mNumStrings = NumStrings;
|
||||
uint NumLanguages = STRG.ReadLong();
|
||||
uint NumStrings = STRG.ReadLong();
|
||||
|
||||
// Language definitions
|
||||
mpStringTable->mLangTables.resize(NumLanguages);
|
||||
std::vector<uint32> LangOffsets(NumLanguages);
|
||||
mpStringTable->mLanguages.resize(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);
|
||||
LangOffsets[iLang] = rSTRG.ReadLong();
|
||||
if (mVersion == EGame::Echoes) rSTRG.Seek(0x4, SEEK_CUR); // Skipping strings size
|
||||
mpStringTable->mLanguages[LanguageIdx].Language = (ELanguage) STRG.ReadFourCC();
|
||||
LanguageOffsets[LanguageIdx] = STRG.ReadLong();
|
||||
|
||||
// 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
|
||||
if (mVersion == EGame::Echoes)
|
||||
LoadNameTable(rSTRG);
|
||||
if (mVersion >= EGame::EchoesDemo)
|
||||
{
|
||||
LoadNameTable(STRG);
|
||||
}
|
||||
|
||||
// Strings
|
||||
uint32 StringsStart = rSTRG.Tell();
|
||||
for (uint32 iLang = 0; iLang < NumLanguages; iLang++)
|
||||
uint StringsStart = STRG.Tell();
|
||||
for (uint32 LanguageIdx = 0; LanguageIdx < NumLanguages; LanguageIdx++)
|
||||
{
|
||||
rSTRG.Seek(StringsStart + LangOffsets[iLang], SEEK_SET);
|
||||
if (mVersion == EGame::Prime) rSTRG.Seek(0x4, SEEK_CUR); // Skipping strings size
|
||||
STRG.GoTo( StringsStart + LanguageOffsets[LanguageIdx] );
|
||||
|
||||
uint32 LangStart = rSTRG.Tell();
|
||||
CStringTable::SLangTable* pLang = &mpStringTable->mLangTables[iLang];
|
||||
pLang->Strings.resize(NumStrings);
|
||||
// Skip strings size in MP1
|
||||
if (mVersion == EGame::Prime)
|
||||
{
|
||||
STRG.Skip(4);
|
||||
}
|
||||
|
||||
CStringTable::SLanguageData& Language = mpStringTable->mLanguages[LanguageIdx];
|
||||
Language.Strings.resize(NumStrings);
|
||||
|
||||
// Offsets
|
||||
std::vector<uint32> StringOffsets(NumStrings);
|
||||
for (uint32 iOff = 0; iOff < NumStrings; iOff++)
|
||||
StringOffsets[iOff] = rSTRG.ReadLong();
|
||||
uint LanguageStart = STRG.Tell();
|
||||
std::vector<uint> StringOffsets(NumStrings);
|
||||
|
||||
for (uint StringIdx = 0; StringIdx < NumStrings; StringIdx++)
|
||||
{
|
||||
StringOffsets[StringIdx] = LanguageStart + STRG.ReadLong();
|
||||
}
|
||||
|
||||
// The actual strings
|
||||
for (uint32 iStr = 0; iStr < NumStrings; iStr++)
|
||||
for (uint StringIdx = 0; StringIdx < NumStrings; StringIdx++)
|
||||
{
|
||||
rSTRG.Seek(LangStart + StringOffsets[iStr], SEEK_SET);
|
||||
pLang->Strings[iStr] = rSTRG.ReadWString().ToUTF8();
|
||||
STRG.GoTo( StringOffsets[StringIdx] );
|
||||
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
|
||||
// Header
|
||||
uint32 NumLanguages = rSTRG.ReadLong();
|
||||
uint32 NumStrings = rSTRG.ReadLong();
|
||||
mpStringTable->mNumStrings = NumStrings;
|
||||
uint NumLanguages = STRG.ReadLong();
|
||||
uint NumStrings = STRG.ReadLong();
|
||||
|
||||
// String names
|
||||
LoadNameTable(rSTRG);
|
||||
LoadNameTable(STRG);
|
||||
|
||||
// Language definitions
|
||||
mpStringTable->mLangTables.resize(NumLanguages);
|
||||
std::vector<std::vector<uint32>> LangOffsets(NumLanguages);
|
||||
mpStringTable->mLanguages.resize(NumLanguages);
|
||||
std::vector< std::vector<uint> > LanguageOffsets(NumLanguages);
|
||||
|
||||
for (uint32 iLang = 0; iLang < NumLanguages; iLang++)
|
||||
mpStringTable->mLangTables[iLang].Language = CFourCC(rSTRG);
|
||||
|
||||
for (uint32 iLang = 0; iLang < NumLanguages; iLang++)
|
||||
for (uint LanguageIdx = 0; LanguageIdx < NumLanguages; LanguageIdx++)
|
||||
{
|
||||
LangOffsets[iLang].resize(NumStrings);
|
||||
|
||||
rSTRG.Seek(0x4, SEEK_CUR); // Skipping total string size
|
||||
|
||||
for (uint32 iStr = 0; iStr < NumStrings; iStr++)
|
||||
LangOffsets[iLang][iStr] = rSTRG.ReadLong();
|
||||
mpStringTable->mLanguages[LanguageIdx].Language = (ELanguage) STRG.ReadFourCC();
|
||||
}
|
||||
|
||||
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
|
||||
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];
|
||||
pLang->Strings.resize(NumStrings);
|
||||
CStringTable::SLanguageData& Language = mpStringTable->mLanguages[LanguageIdx];
|
||||
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);
|
||||
rSTRG.Seek(0x4, SEEK_CUR); // Skipping string size
|
||||
STRG.GoTo( StringsStart + LanguageOffsets[LanguageIdx][StringIdx] );
|
||||
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
|
||||
uint32 NameCount = rSTRG.ReadLong();
|
||||
uint32 NameTableSize = rSTRG.ReadLong();
|
||||
uint32 NameTableStart = rSTRG.Tell();
|
||||
uint32 NameTableEnd = NameTableStart + NameTableSize;
|
||||
uint NameCount = STRG.ReadLong();
|
||||
uint NameTableSize = STRG.ReadLong();
|
||||
uint NameTableStart = STRG.Tell();
|
||||
uint NameTableEnd = NameTableStart + NameTableSize;
|
||||
|
||||
// Name definitions
|
||||
struct SNameDef {
|
||||
uint32 NameOffset, StringIndex;
|
||||
uint NameOffset, StringIndex;
|
||||
};
|
||||
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[iName].StringIndex = rSTRG.ReadLong();
|
||||
NameDefs[NameIdx].NameOffset = STRG.ReadLong() + NameTableStart;
|
||||
NameDefs[NameIdx].StringIndex = STRG.ReadLong();
|
||||
MaxIndex = Math::Max(MaxIndex, (int) NameDefs[NameIdx].StringIndex);
|
||||
}
|
||||
|
||||
// Name strings
|
||||
mpStringTable->mStringNames.resize(mpStringTable->mNumStrings);
|
||||
for (uint32 iName = 0; iName < NameCount; iName++)
|
||||
mpStringTable->mStringNames.resize(MaxIndex + 1);
|
||||
|
||||
for (uint NameIdx = 0; NameIdx < NameCount; NameIdx++)
|
||||
{
|
||||
SNameDef *pDef = &NameDefs[iName];
|
||||
rSTRG.Seek(pDef->NameOffset, SEEK_SET);
|
||||
mpStringTable->mStringNames[pDef->StringIndex] = rSTRG.ReadString();
|
||||
SNameDef& NameDef = NameDefs[NameIdx];
|
||||
STRG.GoTo(NameDef.NameOffset);
|
||||
mpStringTable->mStringNames[NameDef.StringIndex] = STRG.ReadString();
|
||||
}
|
||||
rSTRG.Seek(NameTableEnd, SEEK_SET);
|
||||
STRG.GoTo(NameTableEnd);
|
||||
}
|
||||
|
||||
// ************ STATIC ************
|
||||
CStringTable* CStringLoader::LoadSTRG(IInputStream& rSTRG, CResourceEntry *pEntry)
|
||||
CStringTable* CStringLoader::LoadSTRG(IInputStream& STRG, CResourceEntry* pEntry)
|
||||
{
|
||||
// Verify that this is a valid STRG
|
||||
if (!rSTRG.IsValid()) return nullptr;
|
||||
if (!STRG.IsValid()) return nullptr;
|
||||
|
||||
uint32 Magic = rSTRG.ReadLong();
|
||||
// Verify that this is a valid STRG
|
||||
uint Magic = STRG.ReadLong();
|
||||
EGame Version = EGame::Invalid;
|
||||
|
||||
if (Magic != 0x87654321)
|
||||
{
|
||||
// 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
|
||||
if (Magic <= (uint32) rSTRG.Size())
|
||||
// so the best I can do is verify the first value actually points to the end of the file.
|
||||
// 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;
|
||||
}
|
||||
|
||||
// If not, then we seem to have an invalid file...
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
uint32 FileVersion = rSTRG.ReadLong();
|
||||
uint FileVersion = STRG.ReadLong();
|
||||
Version = GetFormatVersion(FileVersion);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -197,9 +238,9 @@ CStringTable* CStringLoader::LoadSTRG(IInputStream& rSTRG, CResourceEntry *pEntr
|
|||
Loader.mpStringTable = new CStringTable(pEntry);
|
||||
Loader.mVersion = Version;
|
||||
|
||||
if (Version == EGame::PrimeDemo) Loader.LoadPrimeDemoSTRG(rSTRG);
|
||||
else if (Version < EGame::Corruption) Loader.LoadPrimeSTRG(rSTRG);
|
||||
else Loader.LoadCorruptionSTRG(rSTRG);
|
||||
if (Version == EGame::PrimeDemo) Loader.LoadPrimeDemoSTRG(STRG);
|
||||
else if (Version < EGame::Corruption) Loader.LoadPrimeSTRG(STRG);
|
||||
else Loader.LoadCorruptionSTRG(STRG);
|
||||
|
||||
return Loader.mpStringTable;
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
#define CSTRINGLOADER_H
|
||||
|
||||
#include "Core/GameProject/CResourceStore.h"
|
||||
#include "Core/Resource/CStringTable.h"
|
||||
#include "Core/Resource/TResPtr.h"
|
||||
#include "Core/Resource/StringTable/CStringTable.h"
|
||||
#include <Common/EGame.h>
|
||||
|
||||
class CStringLoader
|
||||
|
@ -12,14 +12,14 @@ class CStringLoader
|
|||
EGame mVersion;
|
||||
|
||||
CStringLoader() {}
|
||||
void LoadPrimeDemoSTRG(IInputStream& rSTRG);
|
||||
void LoadPrimeSTRG(IInputStream& rSTRG);
|
||||
void LoadCorruptionSTRG(IInputStream& rSTRG);
|
||||
void LoadNameTable(IInputStream& rSTRG);
|
||||
void LoadPrimeDemoSTRG(IInputStream& STRG);
|
||||
void LoadPrimeSTRG(IInputStream& STRG);
|
||||
void LoadCorruptionSTRG(IInputStream& STRG);
|
||||
void LoadNameTable(IInputStream& STRG);
|
||||
|
||||
public:
|
||||
static CStringTable* LoadSTRG(IInputStream &rSTRG, CResourceEntry *pEntry);
|
||||
static EGame GetFormatVersion(uint32 Version);
|
||||
static CStringTable* LoadSTRG(IInputStream& STRG, CResourceEntry* pEntry);
|
||||
static EGame GetFormatVersion(uint Version);
|
||||
};
|
||||
|
||||
#endif // CSTRINGLOADER_H
|
||||
|
|
|
@ -562,7 +562,7 @@ void CTextureDecoder::ReadPixelC8(IInputStream& rSrc, IOutputStream& rDst)
|
|||
((Index >> 2) & 0x1) ? G = 0xFF : G = 0x0;
|
||||
((Index >> 1) & 0x1) ? B = 0xFF : B = 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);*/
|
||||
|
||||
mPaletteInput.Seek(Index * 2, SEEK_SET);
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
#include "CFont.h"
|
||||
#include "CPoiToWorld.h"
|
||||
#include "CResource.h"
|
||||
#include "CScan.h"
|
||||
#include "CStringTable.h"
|
||||
#include "CTexture.h"
|
||||
#include "CWorld.h"
|
||||
#include "Core/Resource/Animation/CAnimation.h"
|
||||
|
@ -16,6 +14,8 @@
|
|||
#include "Core/Resource/Animation/CSkin.h"
|
||||
#include "Core/Resource/Area/CGameArea.h"
|
||||
#include "Core/Resource/Model/CModel.h"
|
||||
#include "Core/Resource/Scan/CScan.h"
|
||||
#include "Core/Resource/StringTable/CStringTable.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)
|
||||
<< SerialParameter("PropertyArchetypes", mPropertyTemplates)
|
||||
<< SerialParameter("MiscTemplates", mMiscTemplates)
|
||||
<< SerialParameter("States", mStates)
|
||||
<< SerialParameter("Messages", mMessages);
|
||||
}
|
||||
|
@ -51,6 +52,13 @@ void CGameTemplate::Load(const TString& kFilePath)
|
|||
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()
|
||||
|
@ -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)
|
||||
|
@ -286,6 +304,21 @@ bool CGameTemplate::RenamePropertyArchetype(const TString& kTypeName, const TStr
|
|||
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
|
||||
{
|
||||
return mSourceFile.GetFileDirectory();
|
||||
|
|
|
@ -35,54 +35,23 @@ struct SObjId
|
|||
}
|
||||
};
|
||||
|
||||
/** Struct holding a reference to a script object template */
|
||||
struct SScriptTemplatePath
|
||||
/** Struct holding a reference to a template */
|
||||
template<typename TemplateT>
|
||||
struct TTemplatePath
|
||||
{
|
||||
/** File path to the template file, relative to the game directory */
|
||||
TString Path;
|
||||
|
||||
/** Template in memory */
|
||||
std::shared_ptr<CScriptTemplate> pTemplate;
|
||||
std::shared_ptr<TemplateT> pTemplate;
|
||||
|
||||
/** Constructor */
|
||||
SScriptTemplatePath()
|
||||
TTemplatePath()
|
||||
{}
|
||||
|
||||
SScriptTemplatePath(const TString& kInPath, CScriptTemplate* pInTemplate)
|
||||
TTemplatePath(const TString& kInPath, TemplateT* pInTemplate)
|
||||
: Path(kInPath)
|
||||
, pTemplate( std::shared_ptr<CScriptTemplate>(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) )
|
||||
, pTemplate( std::shared_ptr<TemplateT>(pInTemplate) )
|
||||
{}
|
||||
|
||||
/** Serializer */
|
||||
|
@ -92,6 +61,9 @@ struct SPropertyTemplatePath
|
|||
}
|
||||
};
|
||||
|
||||
typedef TTemplatePath<CScriptTemplate> SScriptTemplatePath;
|
||||
typedef TTemplatePath<IProperty> SPropertyTemplatePath;
|
||||
|
||||
/** CGameTemplate - Per-game template data */
|
||||
class CGameTemplate
|
||||
{
|
||||
|
@ -103,6 +75,7 @@ class CGameTemplate
|
|||
/** Template arrays */
|
||||
std::map<SObjId, SScriptTemplatePath> mScriptTemplates;
|
||||
std::map<TString, SPropertyTemplatePath> mPropertyTemplates;
|
||||
std::map<TString, SScriptTemplatePath> mMiscTemplates;
|
||||
|
||||
std::map<SObjId, TString> mStates;
|
||||
std::map<SObjId, TString> mMessages;
|
||||
|
@ -130,6 +103,7 @@ public:
|
|||
IProperty* FindPropertyArchetype(const TString& kTypeName);
|
||||
TString GetPropertyArchetypeFilePath(const TString& kTypeName);
|
||||
bool RenamePropertyArchetype(const TString& kTypeName, const TString& kNewTypeName);
|
||||
CScriptTemplate* FindMiscTemplate(const TString& kTemplateName);
|
||||
TString GetGameDirectory() const;
|
||||
|
||||
// Inline Accessors
|
||||
|
|
|
@ -106,7 +106,8 @@ struct SNameValue
|
|||
bool IsValid;
|
||||
|
||||
/** 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)
|
||||
{
|
||||
|
@ -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. */
|
||||
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);
|
||||
auto MapFind = gNameMap.find(Key);
|
||||
|
@ -293,7 +294,12 @@ void RetrievePropertiesWithID(uint32 ID, const char* pkTypeName, std::list<IProp
|
|||
if (MapFind != gNameMap.end())
|
||||
{
|
||||
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())
|
||||
{
|
||||
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.
|
||||
|
@ -409,7 +415,7 @@ void ChangeTypeName(IProperty* pProperty, const char* pkOldTypeName, const char*
|
|||
|
||||
if (WasRegistered)
|
||||
{
|
||||
Find->second.PropertyList.push_back(pProperty);
|
||||
Find->second.PropertyList.insert(pProperty);
|
||||
}
|
||||
|
||||
gMapIsDirty = true;
|
||||
|
@ -527,7 +533,7 @@ void RegisterProperty(IProperty* pProperty)
|
|||
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.
|
||||
pProperty->SetName( MapFind->second.Name );
|
||||
|
@ -543,7 +549,7 @@ void UnregisterProperty(IProperty* pProperty)
|
|||
{
|
||||
// Found the value, now remove the element from the list.
|
||||
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);
|
||||
|
||||
/** 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. */
|
||||
void RetrieveXMLsWithProperty(uint32 ID, const char* pkTypeName, std::set<TString>& OutSet);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define CANIMATIONSETPROPERTY_H
|
||||
|
||||
#include "IProperty.h"
|
||||
#include "Core/Resource/Animation/CAnimationParameters.h"
|
||||
|
||||
class CAnimationSetProperty : public TSerializeableTypedProperty< CAnimationParameters, EPropertyType::AnimationSet >
|
||||
{
|
||||
|
@ -17,7 +18,7 @@ protected:
|
|||
public:
|
||||
virtual void SerializeValue(void* pData, IArchive& Arc) const
|
||||
{
|
||||
Value(pData).Serialize(Arc);
|
||||
ValueRef(pData).Serialize(Arc);
|
||||
}
|
||||
|
||||
virtual const char* HashableTypeName() const
|
||||
|
|
|
@ -25,7 +25,7 @@ public:
|
|||
|
||||
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
|
||||
TTypedProperty::Serialize(rArc);
|
||||
|
||||
// Serialize default value
|
||||
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);
|
||||
|
||||
// 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 (rkParams.TestIntsAsChoices && NBasics::VectorContains(mTypeNames, TString("int")))
|
||||
if (rkParams.TestIntsAsChoices && NBasics::VectorFind(mTypeNames, TString("int")) >= 0)
|
||||
{
|
||||
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 (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;
|
||||
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
|
||||
{
|
||||
return mpArchetype ? mpArchetype->HashableTypeName() : *mName;
|
||||
|
|
|
@ -25,6 +25,7 @@ public:
|
|||
virtual void Destruct(void* pData) const;
|
||||
virtual bool MatchesDefault(void* pData) const;
|
||||
virtual void RevertToDefault(void* pData) const;
|
||||
virtual void SetDefaultFromData(void* pData);
|
||||
virtual const char* HashableTypeName() const;
|
||||
virtual void Serialize(IArchive& rArc);
|
||||
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.
|
||||
ASSERT(pArchetype != nullptr);
|
||||
ASSERT(pArchetype->Type() == Type());
|
||||
|
||||
InitFromArchetype(pArchetype);
|
||||
}
|
||||
|
@ -325,7 +326,29 @@ TString IProperty::GetTemplateFileName()
|
|||
|
||||
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:
|
||||
return true;
|
||||
|
@ -333,8 +356,13 @@ bool IProperty::ShouldCook(void* pPropertyData) const
|
|||
case ECookPreference::Never:
|
||||
return false;
|
||||
|
||||
case ECookPreference::OnlyIfModified:
|
||||
return !MatchesDefault(pPropertyData);
|
||||
|
||||
default:
|
||||
return (Game() < EGame::DKCReturns ? true : !MatchesDefault(pPropertyData));
|
||||
// Unhandled case
|
||||
ASSERT(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#ifndef IPROPERTY_H
|
||||
#define IPROPERTY_H
|
||||
|
||||
#include "Core/Resource/Animation/CAnimationParameters.h"
|
||||
#include <Common/Common.h>
|
||||
#include <Common/CFourCC.h>
|
||||
#include <Common/Math/CVector3f.h>
|
||||
|
@ -106,7 +105,8 @@ enum class ECookPreference
|
|||
{
|
||||
Default,
|
||||
Always,
|
||||
Never
|
||||
Never,
|
||||
OnlyIfModified
|
||||
};
|
||||
|
||||
/** New property class */
|
||||
|
@ -178,6 +178,7 @@ public:
|
|||
virtual void PostInitialize() {}
|
||||
virtual void PropertyValueChanged(void* pPropertyData) {}
|
||||
virtual void CopyDefaultValueTo(IProperty* pOtherProperty) {}
|
||||
virtual void SetDefaultFromData(void* pData) {}
|
||||
virtual bool IsNumericalType() const { return false; }
|
||||
virtual bool IsPointerType() const { return false; }
|
||||
virtual TString ValueAsString(void* pData) const { return ""; }
|
||||
|
@ -367,6 +368,7 @@ public:
|
|||
virtual void Destruct(void* pData) const { ValueRef(pData).~PropType(); }
|
||||
virtual bool MatchesDefault(void* pData) const { return 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; }
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
//@todo pull these values from tweaks instead of hardcoding them
|
||||
const CColor CPointOfInterestExtra::skRegularColor = CColor::Integral(0xFF,0x70,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)
|
||||
{
|
||||
if (mScanProperty.Property() == pProperty)
|
||||
{
|
||||
mpScanData = gpResourceStore->LoadResource<CScan>( mScanProperty.Get() );
|
||||
mScanIsCritical = (mpScanData ? mpScanData->IsCriticalPropertyRef() : CBoolRef());
|
||||
}
|
||||
}
|
||||
|
||||
void CPointOfInterestExtra::ModifyTintColor(CColor& Color)
|
||||
{
|
||||
if (mpScanData)
|
||||
{
|
||||
if (mpScanData->IsImportant()) Color *= skImportantColor;
|
||||
if (mScanIsCritical) Color *= skImportantColor;
|
||||
else Color *= skRegularColor;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#define CPOINTOFINTERESTEXTRA_H
|
||||
|
||||
#include "CScriptExtra.h"
|
||||
#include "Core/Resource/CScan.h"
|
||||
#include "Core/Resource/Scan/CScan.h"
|
||||
#include <Common/CColor.h>
|
||||
|
||||
class CPointOfInterestExtra : public CScriptExtra
|
||||
|
@ -10,6 +10,7 @@ class CPointOfInterestExtra : public CScriptExtra
|
|||
// Tint POI billboard orange/red depending on scan importance
|
||||
CAssetRef mScanProperty;
|
||||
TResPtr<CScan> mpScanData;
|
||||
CBoolRef mScanIsCritical;
|
||||
|
||||
public:
|
||||
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 "Editor/CharacterEditor/CCharacterEditor.h"
|
||||
#include "Editor/ModelEditor/CModelEditorWindow.h"
|
||||
#include "Editor/ScanEditor/CScanEditor.h"
|
||||
#include "Editor/StringEditor/CStringEditor.h"
|
||||
#include "Editor/ResourceBrowser/CResourceBrowser.h"
|
||||
#include "Editor/WorldEditor/CWorldEditor.h"
|
||||
#include <Common/Macros.h>
|
||||
|
@ -19,6 +21,7 @@ CEditorApplication::CEditorApplication(int& rArgc, char **ppArgv)
|
|||
, mpActiveProject(nullptr)
|
||||
, mpWorldEditor(nullptr)
|
||||
, mpProjectDialog(nullptr)
|
||||
, mInitialized(false)
|
||||
{
|
||||
mLastUpdate = CTimer::GlobalTime();
|
||||
|
||||
|
@ -38,10 +41,14 @@ void CEditorApplication::InitEditor()
|
|||
mpWorldEditor = new CWorldEditor();
|
||||
mpProjectDialog = new CProjectSettingsDialog(mpWorldEditor);
|
||||
mpWorldEditor->showMaximized();
|
||||
mInitialized = true;
|
||||
}
|
||||
|
||||
bool CEditorApplication::CloseAllEditors()
|
||||
{
|
||||
if (!mInitialized)
|
||||
return true;
|
||||
|
||||
// Close active editor windows.
|
||||
foreach (IEditor *pEditor, mEditorWindows)
|
||||
{
|
||||
|
@ -152,11 +159,30 @@ void CEditorApplication::EditResource(CResourceEntry *pEntry)
|
|||
case EResourceType::AnimSet:
|
||||
pEd = new CCharacterEditor((CAnimSet*) pRes, mpWorldEditor);
|
||||
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)
|
||||
{
|
||||
pEd->show();
|
||||
|
||||
if (pEntry->ResourceType() != EResourceType::Tweaks)
|
||||
mEditingMap[pEntry] = pEd;
|
||||
}
|
||||
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
|
||||
CGameProject *pProj = mpActiveProject;
|
||||
mpActiveProject->TweakManager()->ClearTweaks();
|
||||
mpActiveProject = nullptr;
|
||||
emit ActiveProjectChanged(nullptr);
|
||||
|
||||
|
@ -237,6 +264,7 @@ bool CEditorApplication::RebuildResourceDatabase()
|
|||
|
||||
// Set project to active again
|
||||
mpActiveProject = pProj;
|
||||
mpActiveProject->TweakManager()->LoadTweaks();
|
||||
emit ActiveProjectChanged(pProj);
|
||||
|
||||
UICommon::InfoMsg(mpWorldEditor, "Success", "Resource database rebuilt successfully!");
|
||||
|
@ -303,7 +331,11 @@ void CEditorApplication::OnEditorClose()
|
|||
}
|
||||
|
||||
mEditorWindows.removeOne(pEditor);
|
||||
|
||||
if (pEditor != mpWorldEditor->TweakEditor())
|
||||
{
|
||||
delete pEditor;
|
||||
}
|
||||
|
||||
if (mpActiveProject)
|
||||
{
|
||||
|
|
|
@ -25,6 +25,7 @@ class CEditorApplication : public QApplication
|
|||
CProjectSettingsDialog *mpProjectDialog;
|
||||
QVector<IEditor*> mEditorWindows;
|
||||
QMap<CResourceEntry*,IEditor*> mEditingMap;
|
||||
bool mInitialized;
|
||||
|
||||
QTimer mRefreshTimer;
|
||||
double mLastUpdate;
|
||||
|
|
|
@ -33,8 +33,7 @@ CExportGameDialog::CExportGameDialog(const QString& rkIsoPath, const QString& rk
|
|||
mpUI->setupUi(this);
|
||||
|
||||
// Set up disc
|
||||
TWideString StrPath = TO_TWIDESTRING(rkIsoPath);
|
||||
mpDisc = nod::OpenDiscFromImage(*StrPath).release();
|
||||
mpDisc = nod::OpenDiscFromImage(TO_WCHAR(rkIsoPath)).release();
|
||||
|
||||
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
|
||||
if (strcmp(rkHeader.m_gameTitle, "Long Game Name") == 0)
|
||||
{
|
||||
// todo - not handling demos yet
|
||||
return false;
|
||||
// Calculate the CRC of the apploader to figure out which game this is.
|
||||
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;
|
||||
break;
|
||||
}
|
||||
|
||||
case FOURCC('G2MX'):
|
||||
// Echoes, but also appears in the MP3 proto
|
||||
|
@ -203,6 +222,16 @@ bool CExportGameDialog::ValidateGame()
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -164,7 +164,7 @@ void CProjectSettingsDialog::BuildISO()
|
|||
|
||||
// Verify this ISO matches the original
|
||||
bool IsWii;
|
||||
pBaseDisc = nod::OpenDiscFromImage(*TO_TWIDESTRING(SourceIsoPath), IsWii);
|
||||
pBaseDisc = nod::OpenDiscFromImage(TO_WCHAR(SourceIsoPath), 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
|
||||
// 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(rkMessage)) );
|
||||
}
|
||||
|
@ -42,8 +49,17 @@ public:
|
|||
return RetVal;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void AsyncMessageBoxSlot(const QString& rkInfoBoxTitle, const QString& rkMessage)
|
||||
virtual bool OpenProject(const TString& kPath = "")
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
@ -52,6 +68,11 @@ public slots:
|
|||
{
|
||||
return UICommon::YesNoQuestion(gpEdApp->WorldEditor(), rkInfoBoxTitle, rkQuestion);
|
||||
}
|
||||
|
||||
bool OpenProjectSlot(const QString& kPath)
|
||||
{
|
||||
return !kPath.isEmpty() ? gpEdApp->OpenProject(kPath) : UICommon::OpenProject();
|
||||
}
|
||||
};
|
||||
|
||||
#endif // CUIRELAY_H
|
||||
|
|
|
@ -96,14 +96,9 @@ HEADERS += \
|
|||
Undo/CTranslateNodeCommand.h \
|
||||
Undo/EUndoCommand.h \
|
||||
Undo/UndoCommands.h \
|
||||
Widgets/IPreviewPanel.h \
|
||||
Widgets/WColorPicker.h \
|
||||
Widgets/WDraggableSpinBox.h \
|
||||
Widgets/WIntegralSpinBox.h \
|
||||
Widgets/WScanPreviewPanel.h \
|
||||
Widgets/WStringPreviewPanel.h \
|
||||
Widgets/WTextureGLWidget.h \
|
||||
Widgets/WTexturePreviewPanel.h \
|
||||
Widgets/WVectorEditor.h \
|
||||
WorldEditor/CLayerEditor.h \
|
||||
WorldEditor/CLayerModel.h \
|
||||
|
@ -198,7 +193,18 @@ HEADERS += \
|
|||
Widgets/CCheckableTreeWidgetItem.h \
|
||||
Widgets/CCheckableTreeWidget.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
|
||||
SOURCES += \
|
||||
|
@ -207,14 +213,9 @@ SOURCES += \
|
|||
Undo/CRotateNodeCommand.cpp \
|
||||
Undo/CScaleNodeCommand.cpp \
|
||||
Undo/CTranslateNodeCommand.cpp \
|
||||
Widgets/IPreviewPanel.cpp \
|
||||
Widgets/WColorPicker.cpp \
|
||||
Widgets/WDraggableSpinBox.cpp \
|
||||
Widgets/WIntegralSpinBox.cpp \
|
||||
Widgets/WScanPreviewPanel.cpp \
|
||||
Widgets/WStringPreviewPanel.cpp \
|
||||
Widgets/WTextureGLWidget.cpp \
|
||||
Widgets/WTexturePreviewPanel.cpp \
|
||||
Widgets/WVectorEditor.cpp \
|
||||
WorldEditor/CLayerEditor.cpp \
|
||||
WorldEditor/CLayerModel.cpp \
|
||||
|
@ -273,14 +274,18 @@ SOURCES += \
|
|||
ResourceBrowser/CVirtualDirectoryTreeView.cpp \
|
||||
CPropertyNameValidator.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
|
||||
FORMS += \
|
||||
TestDialog.ui \
|
||||
ModelEditor/CModelEditorWindow.ui \
|
||||
Widgets/WScanPreviewPanel.ui \
|
||||
Widgets/WTexturePreviewPanel.ui \
|
||||
WorldEditor/CLayerEditor.ui \
|
||||
WorldEditor/CWorldEditor.ui \
|
||||
WorldEditor/WCreateTab.ui \
|
||||
|
@ -300,7 +305,10 @@ FORMS += \
|
|||
WorldEditor/CPoiMapSidebar.ui \
|
||||
CProgressDialog.ui \
|
||||
Widgets/CSelectResourcePanel.ui \
|
||||
CGeneratePropertyNamesDialog.ui
|
||||
CGeneratePropertyNamesDialog.ui \
|
||||
StringEditor/CStringEditor.ui \
|
||||
CTweakEditor.ui \
|
||||
ScanEditor/CScanEditor.ui
|
||||
|
||||
# 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
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QAction>
|
||||
#include <QList>
|
||||
#include <QUndoStack>
|
||||
|
||||
#include "CEditorApplication.h"
|
||||
|
||||
/** Base class of all editor windows */
|
||||
class IEditor : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
IEditor(QWidget *pParent)
|
||||
: QMainWindow(pParent)
|
||||
{
|
||||
gpEdApp->AddEditor(this);
|
||||
}
|
||||
protected:
|
||||
// Undo stack
|
||||
QUndoStack mUndoStack;
|
||||
QList<QAction*> mUndoActions;
|
||||
|
||||
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 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:
|
||||
void Closed();
|
||||
};
|
||||
|
|
|
@ -15,14 +15,6 @@ INodeEditor::INodeEditor(QWidget *pParent)
|
|||
, mRotateSpace(ETransformSpace::World)
|
||||
, 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
|
||||
mGizmoActions.append(new QAction(QIcon(":/icons/SelectMode.png"), "Select Objects", this));
|
||||
mGizmoActions.append(new QAction(QIcon(":/icons/Translate.png"), "Translate", this));
|
||||
|
@ -63,11 +55,6 @@ INodeEditor::~INodeEditor()
|
|||
delete mpSelection;
|
||||
}
|
||||
|
||||
QUndoStack* INodeEditor::UndoStack()
|
||||
{
|
||||
return &mUndoStack;
|
||||
}
|
||||
|
||||
CScene* INodeEditor::Scene()
|
||||
{
|
||||
return &mScene;
|
||||
|
|
|
@ -12,17 +12,12 @@
|
|||
#include <QActionGroup>
|
||||
#include <QComboBox>
|
||||
#include <QList>
|
||||
#include <QUndoStack>
|
||||
|
||||
class INodeEditor : public IEditor
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
protected:
|
||||
// Undo stack
|
||||
QUndoStack mUndoStack;
|
||||
QList<QAction*> mUndoActions;
|
||||
|
||||
// Node management
|
||||
CScene mScene;
|
||||
CNodeSelection *mpSelection;
|
||||
|
@ -55,7 +50,6 @@ protected:
|
|||
public:
|
||||
explicit INodeEditor(QWidget *pParent = 0);
|
||||
virtual ~INodeEditor();
|
||||
QUndoStack* UndoStack();
|
||||
CScene* Scene();
|
||||
CGizmo* Gizmo();
|
||||
bool IsGizmoVisible();
|
||||
|
|
|
@ -151,6 +151,17 @@ CModelEditorWindow::~CModelEditorWindow()
|
|||
delete ui;
|
||||
}
|
||||
|
||||
bool CModelEditorWindow::Save()
|
||||
{
|
||||
if (!mpCurrentModel) return true;
|
||||
bool SaveSuccess = mpCurrentModel->Entry()->Save();
|
||||
|
||||
if (SaveSuccess)
|
||||
gpEdApp->NotifyAssetsModified();
|
||||
|
||||
return SaveSuccess;
|
||||
}
|
||||
|
||||
void CModelEditorWindow::RefreshViewport()
|
||||
{
|
||||
ui->Viewport->ProcessInput();
|
||||
|
@ -749,15 +760,6 @@ void CModelEditorWindow::Import()
|
|||
gpResourceStore->DestroyUnreferencedResources();
|
||||
}
|
||||
|
||||
void CModelEditorWindow::Save()
|
||||
{
|
||||
if (!mpCurrentModel) return;
|
||||
bool SaveSuccess = mpCurrentModel->Entry()->Save();
|
||||
|
||||
if (SaveSuccess)
|
||||
gpEdApp->NotifyAssetsModified();
|
||||
}
|
||||
|
||||
void CModelEditorWindow::ConvertToDDS()
|
||||
{
|
||||
QString Input = QFileDialog::getOpenFileName(this, "Retro Texture (*.TXTR)", "", "*.TXTR");
|
||||
|
|
|
@ -34,6 +34,7 @@ class CModelEditorWindow : public IEditor
|
|||
public:
|
||||
explicit CModelEditorWindow(CModel *pModel, QWidget *pParent = 0);
|
||||
~CModelEditorWindow();
|
||||
bool Save();
|
||||
void SetActiveModel(CModel *pModel);
|
||||
CModelEditorViewport* Viewport() const;
|
||||
|
||||
|
@ -100,7 +101,6 @@ private:
|
|||
|
||||
private slots:
|
||||
void Import();
|
||||
void Save();
|
||||
void ConvertToDDS();
|
||||
void ConvertToTXTR();
|
||||
void SetMeshPreview();
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "Editor/UICommon.h"
|
||||
#include "Editor/Undo/CEditScriptPropertyCommand.h"
|
||||
#include "Editor/Undo/CEditIntrinsicPropertyCommand.h"
|
||||
#include "Editor/Undo/CResizeScriptArrayCommand.h"
|
||||
#include "Editor/Widgets/CResourceSelector.h"
|
||||
#include "Editor/Widgets/WColorPicker.h"
|
||||
|
@ -33,6 +34,12 @@ CPropertyDelegate::CPropertyDelegate(QObject *pParent /*= 0*/)
|
|||
, mEditInProgress(false)
|
||||
, mRelaysBlocked(false)
|
||||
{
|
||||
mpEditor = UICommon::FindAncestor<IEditor>(pParent);
|
||||
}
|
||||
|
||||
void CPropertyDelegate::SetEditor(IEditor* pEditor)
|
||||
{
|
||||
mpEditor = pEditor;
|
||||
}
|
||||
|
||||
void CPropertyDelegate::SetPropertyModel(CPropertyModel* pModel)
|
||||
|
@ -40,11 +47,6 @@ void CPropertyDelegate::SetPropertyModel(CPropertyModel *pModel)
|
|||
mpModel = pModel;
|
||||
}
|
||||
|
||||
void CPropertyDelegate::SetEditor(CWorldEditor *pEditor)
|
||||
{
|
||||
mpEditor = pEditor;
|
||||
}
|
||||
|
||||
QWidget* CPropertyDelegate::createEditor(QWidget* pParent, const QStyleOptionViewItem& /*rkOption*/, const QModelIndex& rkIndex) const
|
||||
{
|
||||
if (!mpModel) return nullptr;
|
||||
|
@ -366,16 +368,28 @@ void CPropertyDelegate::setModelData(QWidget *pEditor, QAbstractItemModel* /*pMo
|
|||
if (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;
|
||||
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)
|
||||
{
|
||||
// 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
|
||||
if (rkIndex.internalId() & 0x80000000)
|
||||
{
|
||||
|
@ -487,9 +501,6 @@ void CPropertyDelegate::setModelData(QWidget *pEditor, QAbstractItemModel* /*pMo
|
|||
// Array
|
||||
else
|
||||
{
|
||||
pCommand = new CResizeScriptArrayCommand(pProp, mpEditor, Objects, mpModel, rkIndex);
|
||||
pCommand->SaveOldData();
|
||||
|
||||
WIntegralSpinBox* pSpinBox = static_cast<WIntegralSpinBox*>(pEditor);
|
||||
CArrayProperty* pArray = static_cast<CArrayProperty*>(pProp);
|
||||
int OldCount = pArray->ArrayCount(pData);
|
||||
|
@ -525,7 +536,7 @@ void CPropertyDelegate::setModelData(QWidget *pEditor, QAbstractItemModel* /*pMo
|
|||
{
|
||||
// Always consider the edit done for bool properties
|
||||
pCommand->SetEditComplete(!mEditInProgress || pProp->Type() == EPropertyType::Bool);
|
||||
mpEditor->UndoStack()->push(pCommand);
|
||||
mpEditor->UndoStack().push(pCommand);
|
||||
}
|
||||
|
||||
else
|
||||
|
@ -565,9 +576,9 @@ QWidget* CPropertyDelegate::CreateCharacterEditor(QWidget *pParent, const QModel
|
|||
pSelector->SetFrameVisible(false);
|
||||
|
||||
if (Params.Version() <= EGame::Echoes)
|
||||
pSelector->SetTypeFilter(mpEditor->CurrentGame(), "ANCS");
|
||||
pSelector->SetTypeFilter(gpEdApp->CurrentGame(), "ANCS");
|
||||
else
|
||||
pSelector->SetTypeFilter(mpEditor->CurrentGame(), "CHAR");
|
||||
pSelector->SetTypeFilter(gpEdApp->CurrentGame(), "CHAR");
|
||||
|
||||
CONNECT_RELAY(pSelector, rkIndex, ResourceChanged(CResourceEntry*));
|
||||
return pSelector;
|
||||
|
@ -631,7 +642,7 @@ void CPropertyDelegate::SetCharacterModelData(QWidget *pEditor, const QModelInde
|
|||
if (Type == EPropertyType::Asset)
|
||||
{
|
||||
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)
|
||||
|
|
|
@ -9,7 +9,7 @@ class CPropertyDelegate : public QStyledItemDelegate
|
|||
{
|
||||
Q_OBJECT
|
||||
|
||||
CWorldEditor *mpEditor;
|
||||
IEditor* mpEditor;
|
||||
CPropertyModel* mpModel;
|
||||
bool mInRelayWidgetEdit;
|
||||
mutable bool mEditInProgress;
|
||||
|
@ -17,8 +17,8 @@ class CPropertyDelegate : public QStyledItemDelegate
|
|||
|
||||
public:
|
||||
CPropertyDelegate(QObject* pParent = 0);
|
||||
void SetEditor(IEditor* pEditor);
|
||||
void SetPropertyModel(CPropertyModel* pModel);
|
||||
void SetEditor(CWorldEditor *pEditor);
|
||||
|
||||
virtual QWidget* createEditor(QWidget* pParent, const QStyleOptionViewItem& rkOption, const QModelIndex& rkIndex) const;
|
||||
virtual void setEditorData(QWidget* pEditor, const QModelIndex& rkIndex) const;
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
CPropertyView::CPropertyView(QWidget *pParent)
|
||||
: QTreeView(pParent)
|
||||
, mpEditor(nullptr)
|
||||
, mpMenuProperty(nullptr)
|
||||
{
|
||||
mpModel = new CPropertyModel(this);
|
||||
|
@ -80,21 +79,36 @@ bool CPropertyView::event(QEvent *pEvent)
|
|||
pEvent->ignore();
|
||||
return true;
|
||||
}
|
||||
|
||||
else return QTreeView::event(pEvent);
|
||||
else if (pEvent->type() == QEvent::Resize && !isVisible())
|
||||
{
|
||||
resizeColumnToContents(0);
|
||||
}
|
||||
|
||||
void CPropertyView::SetEditor(CWorldEditor *pEditor)
|
||||
return QTreeView::event(pEvent);
|
||||
}
|
||||
|
||||
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);
|
||||
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)
|
||||
{
|
||||
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());
|
||||
SetPersistentEditors(QModelIndex());
|
||||
}
|
||||
|
@ -102,7 +116,7 @@ void CPropertyView::SetIntrinsicProperties(CStructRef InProperties)
|
|||
void CPropertyView::SetInstance(CScriptObject *pObj)
|
||||
{
|
||||
mpObject = pObj;
|
||||
mpModel->SetBoldModifiedProperties(mpEditor ? (mpEditor->CurrentGame() > EGame::Prime) : true);
|
||||
mpModel->SetBoldModifiedProperties(gpEdApp->CurrentGame() > EGame::Prime);
|
||||
|
||||
if (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)
|
||||
{
|
||||
// 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.
|
||||
for (int iRow = 0; iRow < mpModel->rowCount(rkParent); iRow++)
|
||||
|
@ -179,8 +193,7 @@ void CPropertyView::SetPersistentEditors(const QModelIndex& rkParent)
|
|||
|
||||
if (pProp->Type() == EPropertyType::AnimationSet)
|
||||
{
|
||||
EGame Game = mpObject->Area()->Game();
|
||||
Type = mpDelegate->DetermineCharacterPropType(Game, ChildIndex);
|
||||
Type = mpDelegate->DetermineCharacterPropType(pProp->Game(), ChildIndex);
|
||||
IsAnimSet = true;
|
||||
}
|
||||
|
||||
|
@ -237,6 +250,10 @@ void CPropertyView::OnPropertyModified(const QModelIndex& rkIndex)
|
|||
ClosePersistentEditors(rkIndex);
|
||||
SetPersistentEditors(rkIndex);
|
||||
}
|
||||
|
||||
scrollTo(rkIndex);
|
||||
emit PropertyModified(rkIndex);
|
||||
emit PropertyModified(pProperty);
|
||||
}
|
||||
|
||||
void CPropertyView::RefreshView()
|
||||
|
@ -260,7 +277,7 @@ void CPropertyView::CreateContextMenu(const QPoint& rkPos)
|
|||
Menu.addAction(mpEditTemplateAction);
|
||||
}
|
||||
|
||||
if (mpEditor->CurrentGame() >= EGame::EchoesDemo)
|
||||
if (gpEdApp->CurrentGame() >= EGame::EchoesDemo)
|
||||
{
|
||||
Menu.addAction(mpShowNameValidityAction);
|
||||
}
|
||||
|
@ -297,7 +314,8 @@ void CPropertyView::ToggleShowNameValidity(bool ShouldShow)
|
|||
|
||||
void CPropertyView::EditPropertyTemplate()
|
||||
{
|
||||
CTemplateEditDialog Dialog(mpMenuProperty, mpEditor);
|
||||
QMainWindow* pParentWindow = UICommon::FindAncestor<QMainWindow>(this);
|
||||
CTemplateEditDialog Dialog(mpMenuProperty, pParentWindow);
|
||||
connect(&Dialog, SIGNAL(PerformedTypeConversion()), this, SLOT(RefreshView()));
|
||||
Dialog.exec();
|
||||
}
|
||||
|
@ -305,21 +323,21 @@ void CPropertyView::EditPropertyTemplate()
|
|||
|
||||
void CPropertyView::GenerateNamesForProperty()
|
||||
{
|
||||
CGeneratePropertyNamesDialog* pDialog = mpEditor->NameGeneratorDialog();
|
||||
CGeneratePropertyNamesDialog* pDialog = gpEdApp->WorldEditor()->NameGeneratorDialog();
|
||||
pDialog->AddToIDPool(mpMenuProperty);
|
||||
pDialog->show();
|
||||
}
|
||||
|
||||
void CPropertyView::GenerateNamesForSiblings()
|
||||
{
|
||||
CGeneratePropertyNamesDialog* pDialog = mpEditor->NameGeneratorDialog();
|
||||
CGeneratePropertyNamesDialog* pDialog = gpEdApp->WorldEditor()->NameGeneratorDialog();
|
||||
pDialog->AddChildrenToIDPool(mpMenuProperty->Parent(), false);
|
||||
pDialog->show();
|
||||
}
|
||||
|
||||
void CPropertyView::GenerateNamesForChildren()
|
||||
{
|
||||
CGeneratePropertyNamesDialog* pDialog = mpEditor->NameGeneratorDialog();
|
||||
CGeneratePropertyNamesDialog* pDialog = gpEdApp->WorldEditor()->NameGeneratorDialog();
|
||||
pDialog->AddChildrenToIDPool(mpMenuProperty, false);
|
||||
pDialog->show();
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ class CPropertyView : public QTreeView
|
|||
{
|
||||
Q_OBJECT
|
||||
|
||||
CWorldEditor *mpEditor;
|
||||
CPropertyModel* mpModel;
|
||||
CPropertyDelegate* mpDelegate;
|
||||
CScriptObject* mpObject;
|
||||
|
@ -26,7 +25,10 @@ public:
|
|||
CPropertyView(QWidget* pParent = 0);
|
||||
void setModel(QAbstractItemModel* pModel);
|
||||
bool event(QEvent* pEvent);
|
||||
void SetEditor(CWorldEditor *pEditor);
|
||||
int sizeHintForColumn(int Column) const;
|
||||
|
||||
void SetEditor(IEditor* pEditor);
|
||||
void ClearProperties();
|
||||
void SetIntrinsicProperties(CStructRef InProperties);
|
||||
void SetInstance(CScriptObject* pObj);
|
||||
void UpdateEditorProperties(const QModelIndex& rkParent);
|
||||
|
@ -46,6 +48,10 @@ public slots:
|
|||
void GenerateNamesForProperty();
|
||||
void GenerateNamesForSiblings();
|
||||
void GenerateNamesForChildren();
|
||||
|
||||
signals:
|
||||
void PropertyModified(const QModelIndex& kIndex);
|
||||
void PropertyModified(IProperty* pProperty);
|
||||
};
|
||||
|
||||
#endif // CPROPERTYVIEW_H
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
#include "Editor/Undo/CMoveResourceCommand.h"
|
||||
#include "Editor/Undo/CRenameDirectoryCommand.h"
|
||||
#include "Editor/Undo/CRenameResourceCommand.h"
|
||||
#include "Editor/Undo/CSaveStoreCommand.h"
|
||||
#include "Editor/Undo/ICreateDeleteDirectoryCommand.h"
|
||||
#include "Editor/Undo/ICreateDeleteResourceCommand.h"
|
||||
#include <Core/GameProject/AssetNameGeneration.h>
|
||||
#include <Core/GameProject/CAssetNameMap.h>
|
||||
|
||||
|
@ -28,6 +30,7 @@ CResourceBrowser::CResourceBrowser(QWidget *pParent)
|
|||
, mEditorStore(false)
|
||||
, mAssetListMode(false)
|
||||
, mSearching(false)
|
||||
, mpAddMenu(nullptr)
|
||||
, mpInspectedEntry(nullptr)
|
||||
{
|
||||
mpUI->setupUi(this);
|
||||
|
@ -150,7 +153,6 @@ CResourceBrowser::CResourceBrowser(QWidget *pParent)
|
|||
connect(mpUI->ResourceTreeButton, SIGNAL(pressed()), this, SLOT(SetResourceTreeView()));
|
||||
connect(mpUI->ResourceListButton, SIGNAL(pressed()), this, SLOT(SetResourceListView()));
|
||||
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->DirectoryTreeView, SIGNAL(clicked(QModelIndex)), this, SLOT(OnDirectorySelectionChanged(QModelIndex)));
|
||||
|
@ -281,6 +283,50 @@ void CResourceBrowser::CreateFilterCheckboxes()
|
|||
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)
|
||||
{
|
||||
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
|
||||
mUndoStack.beginMacro("Rename Resource");
|
||||
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||
mUndoStack.push( new CRenameResourceCommand(pEntry, rkNewName) );
|
||||
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||
mUndoStack.endMacro();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -325,7 +375,11 @@ bool CResourceBrowser::RenameDirectory(CVirtualDirectory *pDir, const TString& r
|
|||
}
|
||||
|
||||
// No conflicts, proceed with the rename
|
||||
mUndoStack.beginMacro("Rename Directory");
|
||||
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||
mUndoStack.push( new CRenameDirectoryCommand(pDir, rkNewName) );
|
||||
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||
mUndoStack.endMacro();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -387,6 +441,7 @@ bool CResourceBrowser::MoveResources(const QList<CResourceEntry*>& rkResources,
|
|||
if (!ValidResources.isEmpty() || !ValidDirs.isEmpty())
|
||||
{
|
||||
mUndoStack.beginMacro("Move Resources");
|
||||
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||
|
||||
foreach (CVirtualDirectory *pDir, ValidDirs)
|
||||
mUndoStack.push( new CMoveDirectoryCommand(mpStore, pDir, pNewDir) );
|
||||
|
@ -394,12 +449,71 @@ bool CResourceBrowser::MoveResources(const QList<CResourceEntry*>& rkResources,
|
|||
foreach (CResourceEntry *pEntry, ValidResources)
|
||||
mUndoStack.push( new CMoveResourceCommand(pEntry, pNewDir) );
|
||||
|
||||
mUndoStack.push( new CSaveStoreCommand(mpStore) );
|
||||
mUndoStack.endMacro();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (pWatched == mpUI->ResourceTableView)
|
||||
|
@ -498,6 +612,23 @@ void CResourceBrowser::OnSortModeChanged(int Index)
|
|||
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()
|
||||
{
|
||||
if (mpSelectedDir)
|
||||
|
@ -513,8 +644,12 @@ bool CResourceBrowser::CreateDirectory()
|
|||
}
|
||||
|
||||
// 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);
|
||||
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
|
||||
CVirtualDirectory *pNewDir = mpSelectedDir->FindChildDirectory(DirName, false);
|
||||
|
@ -537,28 +672,101 @@ bool CResourceBrowser::CreateDirectory()
|
|||
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))
|
||||
DeletableDirs << pDir;
|
||||
if (!Directories[DirIdx]->IsSafeToDelete())
|
||||
{
|
||||
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 CSaveStoreCommand(mpStore) );
|
||||
mUndoStack.endMacro();
|
||||
return true;
|
||||
}
|
||||
|
||||
else return false;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void CResourceBrowser::OnSearchStringChanged(QString SearchString)
|
||||
|
@ -685,7 +893,8 @@ void CResourceBrowser::UpdateStore()
|
|||
mpUI->SearchBar->clear();
|
||||
mSearching = false;
|
||||
|
||||
// Refresh type filter list
|
||||
// Refresh project-specific UI
|
||||
CreateAddMenu();
|
||||
CreateFilterCheckboxes();
|
||||
|
||||
// Refresh directory tree
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue