mirror of
https://github.com/AxioDL/PrimeWorldEditor.git
synced 2025-12-21 02:39:17 +00:00
Merge branch 'StringEditing'
This commit is contained in:
@@ -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,10 +312,15 @@ void GenerateAssetNames(CGameProject *pProj)
|
||||
ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), ScanName);
|
||||
|
||||
CScan *pScan = (CScan*) pEntry->Load();
|
||||
if (pScan && pScan->ScanText())
|
||||
if (pScan)
|
||||
{
|
||||
CResourceEntry *pStringEntry = pScan->ScanText()->Entry();
|
||||
ApplyGeneratedName(pStringEntry, pStringEntry->DirectoryPath(), ScanName);
|
||||
CAssetID StringID = pScan->ScanStringPropertyRef();
|
||||
CResourceEntry* pStringEntry = gpResourceStore->FindEntry(StringID);
|
||||
|
||||
if (pStringEntry)
|
||||
{
|
||||
ApplyGeneratedName(pStringEntry, pStringEntry->DirectoryPath(), ScanName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,12 +30,12 @@ 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)
|
||||
{
|
||||
CMemoryOutStream Banner(mBannerData.data(), mBannerData.size(), EEndian::BigEndian);
|
||||
CMemoryOutStream Banner(mBannerData.data(), mBannerData.size(), EEndian::BigEndian);
|
||||
uint32 PadCount = 0;
|
||||
|
||||
uint32 MaxLen = MaxGameNameLength();
|
||||
@@ -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,7 +309,10 @@ bool CResourceEntry::Save(bool SkipCacheSave /*= false*/)
|
||||
return false;
|
||||
}
|
||||
|
||||
SetFlag(EResEntryFlag::NeedsRecook);
|
||||
if (FlagForRecook)
|
||||
{
|
||||
SetFlag(EResEntryFlag::NeedsRecook);
|
||||
}
|
||||
}
|
||||
|
||||
// This resource type doesn't have a raw format; save cooked instead
|
||||
@@ -321,13 +335,16 @@ bool CResourceEntry::Save(bool SkipCacheSave /*= false*/)
|
||||
if (!SkipCacheSave)
|
||||
{
|
||||
mpStore->ConditionalSaveStore();
|
||||
}
|
||||
|
||||
// Flag dirty any packages that contain this resource.
|
||||
// Flag dirty any packages that contain this resource.
|
||||
if (FlagForRecook)
|
||||
{
|
||||
for (uint32 iPkg = 0; iPkg < mpStore->Project()->NumPackages(); iPkg++)
|
||||
{
|
||||
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() ); }
|
||||
@@ -103,7 +106,7 @@ public:
|
||||
inline TString DirectoryPath() const { return mpDirectory->FullPath(); }
|
||||
inline TString Name() const { return mName; }
|
||||
inline const TString& UppercaseName() const { return mCachedUppercaseName; }
|
||||
inline EResourceType ResourceType() const { return mpTypeInfo->Type(); }
|
||||
inline EResourceType ResourceType() const { return mpTypeInfo->Type(); }
|
||||
|
||||
protected:
|
||||
CResource* InternalLoad(IInputStream& rInput);
|
||||
|
||||
@@ -22,12 +22,16 @@ public:
|
||||
|
||||
virtual CResourceEntry* Next()
|
||||
{
|
||||
if (mIter != mpkStore->mResourceEntries.end())
|
||||
do
|
||||
{
|
||||
mpCurEntry = mIter->second;
|
||||
mIter++;
|
||||
if (mIter != mpkStore->mResourceEntries.end())
|
||||
{
|
||||
mpCurEntry = mIter->second;
|
||||
mIter++;
|
||||
}
|
||||
else mpCurEntry = nullptr;
|
||||
}
|
||||
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())
|
||||
@@ -85,10 +101,13 @@ bool CResourceStore::SerializeDatabaseCache(IArchive& rArc)
|
||||
{
|
||||
for (CResourceIterator It(this); It; ++It)
|
||||
{
|
||||
if (rArc.ParamBegin("Resource", 0))
|
||||
if (!It->IsMarkedForDeletion())
|
||||
{
|
||||
It->SerializeEntryInfo(rArc, false);
|
||||
rArc.ParamEnd();
|
||||
if (rArc.ParamBegin("Resource", 0))
|
||||
{
|
||||
It->SerializeEntryInfo(rArc, false);
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
auto Found = mResourceEntries.find(rkID);
|
||||
if (Found == mResourceEntries.end()) return nullptr;
|
||||
else return Found->second;
|
||||
if (rkID.IsValid())
|
||||
{
|
||||
auto Found = mResourceEntries.find(rkID);
|
||||
|
||||
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;
|
||||
|
||||
|
||||
207
src/Core/NCoreTests.cpp
Normal file
207
src/Core/NCoreTests.cpp
Normal file
@@ -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
|
||||
18
src/Core/NCoreTests.h
Normal file
18
src/Core/NCoreTests.h
Normal file
@@ -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;
|
||||
|
||||
@@ -36,10 +37,11 @@ public:
|
||||
CFourCC CookedExtension(EGame Game) const;
|
||||
|
||||
// Accessors
|
||||
inline EResourceType Type() const { return mType; }
|
||||
inline EResourceType Type() const { return mType; }
|
||||
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:
|
||||
|
||||
63
src/Core/Resource/Cooker/CScanCooker.cpp
Normal file
63
src/Core/Resource/Cooker/CScanCooker.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "CScanCooker.h"
|
||||
#include "Core/Resource/Cooker/CScriptCooker.h"
|
||||
#include "Core/GameProject/DependencyListBuilders.h"
|
||||
|
||||
bool CScanCooker::CookSCAN(CScan* pScan, IOutputStream& SCAN)
|
||||
{
|
||||
// File header
|
||||
if (pScan->Game() <= EGame::Prime)
|
||||
{
|
||||
// We currently do not support cooking for the MP1 demo build
|
||||
ASSERT( pScan->Game() != EGame::PrimeDemo );
|
||||
SCAN.WriteLong( 5 ); // Version number; must be 5
|
||||
SCAN.WriteLong( 0x0BADBEEF ); // SCAN magic
|
||||
|
||||
CStructRef ScanProperties = pScan->ScanData();
|
||||
CScriptCooker Cooker(pScan->Game());
|
||||
Cooker.WriteProperty(SCAN, ScanProperties.Property(), ScanProperties.DataPointer(), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
SCAN.WriteFourCC( FOURCC('SCAN') ); // SCAN magic
|
||||
SCAN.WriteLong( 2 ); // Version number; must be 2
|
||||
SCAN.WriteByte( 1 ); // Layer version number; must be 1
|
||||
SCAN.WriteLong( 1 ); // Instance count
|
||||
|
||||
// Scans in MP2/3 are saved with the script object data format
|
||||
// Write a dummy script object header here
|
||||
SCAN.WriteLong( FOURCC('SNFO') ); // ScannableObjectInfo object ID
|
||||
uint ScanInstanceSizeOffset = SCAN.Tell();
|
||||
SCAN.WriteShort( 0 ); // Object size
|
||||
SCAN.WriteLong( 0 ); // Instance ID
|
||||
SCAN.WriteShort( 0 ); // Link count
|
||||
|
||||
CStructRef ScanProperties = pScan->ScanData();
|
||||
CScriptCooker Cooker(pScan->Game());
|
||||
Cooker.WriteProperty(SCAN, ScanProperties.Property(), ScanProperties.DataPointer(), false);
|
||||
|
||||
uint ScanInstanceEnd = SCAN.Tell();
|
||||
uint ScanInstanceSize = ScanInstanceEnd - ScanInstanceSizeOffset - 2;
|
||||
SCAN.GoTo(ScanInstanceSizeOffset);
|
||||
SCAN.WriteShort( (uint16) ScanInstanceSize );
|
||||
SCAN.GoTo(ScanInstanceEnd);
|
||||
|
||||
// Write dependency list
|
||||
// @todo this output may not be 100% correct. Some dependencies seem to be conditionally excluded in base game.
|
||||
// This may cause some assets to be unnecessarily loaded into memory ingame.
|
||||
std::vector<CAssetID> Dependencies;
|
||||
CAssetDependencyListBuilder Builder(pScan->Entry());
|
||||
Builder.BuildDependencyList(Dependencies);
|
||||
SCAN.WriteLong(Dependencies.size());
|
||||
|
||||
for (const CAssetID& kID : Dependencies)
|
||||
{
|
||||
CResourceEntry* pEntry = pScan->Entry()->ResourceStore()->FindEntry(kID);
|
||||
ASSERT( pEntry != nullptr );
|
||||
|
||||
pEntry->CookedExtension().Write(SCAN);
|
||||
kID.Write(SCAN);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
15
src/Core/Resource/Cooker/CScanCooker.h
Normal file
15
src/Core/Resource/Cooker/CScanCooker.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef CSCANCOOKER_H
|
||||
#define CSCANCOOKER_H
|
||||
|
||||
#include "Core/Resource/Scan/CScan.h"
|
||||
|
||||
/** Cooker class for writing game-compatible SCAN assets */
|
||||
class CScanCooker
|
||||
{
|
||||
CScanCooker() {}
|
||||
|
||||
public:
|
||||
static bool CookSCAN(CScan* pScan, IOutputStream& SCAN);
|
||||
};
|
||||
|
||||
#endif // CSCANCOOKER_H
|
||||
@@ -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);
|
||||
|
||||
328
src/Core/Resource/Cooker/CStringCooker.cpp
Normal file
328
src/Core/Resource/Cooker/CStringCooker.cpp
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
25
src/Core/Resource/Cooker/CStringCooker.h
Normal file
25
src/Core/Resource/Cooker/CStringCooker.h
Normal file
@@ -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)
|
||||
mGame = (mpInput->PeekShort() == 0x0101 ? EGame::Echoes : EGame::Prime);
|
||||
// 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)
|
||||
{
|
||||
errorf("%s: Unsupported SCAN version: 0x%X", *rSCAN.GetSourceString(), FileVersion);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// MP1 SCAN - read the file!
|
||||
CScanLoader Loader;
|
||||
Loader.mVersion = EGame::Prime;
|
||||
Loader.mpScan = new CScan(pEntry);
|
||||
Loader.mpEntry = pEntry;
|
||||
return Loader.LoadScanMP1(rSCAN);
|
||||
// The SCAN format in MP2 and later games uses the script loader to load SCAN parameters.
|
||||
// The MP1 format is not loaded the same way, as far as I'm aware, and is loaded the same
|
||||
// way as a normal file format... however, since we support all games, we need to support
|
||||
// the script object method for proper MP2/3 support (including dealing with property names/IDs).
|
||||
// So, it's simplest to use the script loader to load the MP1 SCAN format as well... that enables
|
||||
// us to just create one class for all SCAN assets that works for every game.
|
||||
mpScan = new CScan(pEntry);
|
||||
CScriptLoader::LoadStructData(SCAN, mpScan->ScanData());
|
||||
return mpScan;
|
||||
}
|
||||
|
||||
CScan* CScanLoader::LoadScanMP2(IInputStream& SCAN, CResourceEntry* pEntry)
|
||||
{
|
||||
// Validate version
|
||||
uint Version = SCAN.ReadLong();
|
||||
|
||||
if (Version != 2)
|
||||
{
|
||||
errorf("Unrecognized SCAN version: %d", Version);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// The SCAN format in MP2 embeds a ScannableObjectInfo script object using the same file format as SCLY.
|
||||
// As such we use CScriptLoader to load parameters, but since we don't actually want to create a script
|
||||
// object, we will skip past the script object/layer header and just load the properties directly.
|
||||
SCAN.Skip(0x17);
|
||||
mpScan = new CScan(pEntry);
|
||||
CScriptLoader::LoadStructData(SCAN, mpScan->ScanData());
|
||||
return mpScan;
|
||||
}
|
||||
|
||||
// ************ STATIC/PUBLIC ************
|
||||
CScan* CScanLoader::LoadSCAN(IInputStream& SCAN, CResourceEntry *pEntry)
|
||||
{
|
||||
if (!SCAN.IsValid()) return nullptr;
|
||||
|
||||
// MP1 SCAN format starts with a version number and then follows with a magic.
|
||||
// The demo can be 1, 2, or 3, while the final build is 5.
|
||||
// The MP2 SCAN format starts with a 'SCAN' magic.
|
||||
uint VersionCheck = SCAN.ReadLong();
|
||||
|
||||
// Echoes+
|
||||
if (VersionCheck == FOURCC('SCAN'))
|
||||
{
|
||||
CScanLoader Loader;
|
||||
return Loader.LoadScanMP2(SCAN, pEntry);
|
||||
}
|
||||
// MP1
|
||||
else if (VersionCheck <= 5)
|
||||
{
|
||||
if (VersionCheck == 5)
|
||||
{
|
||||
CScanLoader Loader;
|
||||
return Loader.LoadScanMP1(SCAN, pEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
errorf("%s: Unsupported SCAN version: %d", VersionCheck);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errorf("Failed to identify SCAN version: 0x%X", VersionCheck);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
// Strings
|
||||
uint32 StringsStart = rSTRG.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);
|
||||
LanguageOffsets[LanguageIdx].resize(NumStrings);
|
||||
STRG.Skip(4); // Skipping total string size
|
||||
|
||||
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
|
||||
LanguageOffsets[LanguageIdx][StringIdx] = STRG.ReadLong();
|
||||
}
|
||||
}
|
||||
|
||||
pLang->Strings[iStr] = rSTRG.ReadString();
|
||||
// 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 LanguageIdx = 0; LanguageIdx < NumLanguages; LanguageIdx++)
|
||||
{
|
||||
CStringTable::SLanguageData& Language = mpStringTable->mLanguages[LanguageIdx];
|
||||
Language.Strings.resize(NumStrings);
|
||||
|
||||
for (uint StringIdx = 0; StringIdx < NumStrings; StringIdx++)
|
||||
{
|
||||
STRG.GoTo( StringsStart + LanguageOffsets[LanguageIdx][StringIdx] );
|
||||
STRG.Skip(4); // Skipping string size
|
||||
Language.Strings[StringIdx].String = STRG.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;
|
||||
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
|
||||
|
||||
|
||||
51
src/Core/Resource/Scan/CScan.cpp
Normal file
51
src/Core/Resource/Scan/CScan.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#include "CScan.h"
|
||||
|
||||
CScan::CScan(CResourceEntry* pEntry /*= 0*/)
|
||||
: CResource(pEntry)
|
||||
{
|
||||
CGameTemplate* pGameTemplate = NGameList::GetGameTemplate( Game() );
|
||||
mpTemplate = pGameTemplate->FindMiscTemplate("ScannableObjectInfo");
|
||||
ASSERT( mpTemplate != nullptr );
|
||||
|
||||
CStructProperty* pProperties = mpTemplate->Properties();
|
||||
mPropertyData.resize( pProperties->DataSize() );
|
||||
pProperties->Construct( mPropertyData.data() );
|
||||
}
|
||||
|
||||
CStructRef CScan::ScanData() const
|
||||
{
|
||||
return CStructRef((void*) mPropertyData.data(), mpTemplate->Properties());
|
||||
}
|
||||
|
||||
/** Convenience property accessors */
|
||||
CAssetRef CScan::ScanStringPropertyRef() const
|
||||
{
|
||||
const uint kStringIdMP1 = 0x1;
|
||||
const uint kStringIdMP2 = 0x2F5B6423;
|
||||
|
||||
IProperty* pProperty = mpTemplate->Properties()->ChildByID(
|
||||
Game() <= EGame::Prime ? kStringIdMP1 : kStringIdMP2
|
||||
);
|
||||
|
||||
return CAssetRef( (void*) mPropertyData.data(), pProperty );
|
||||
}
|
||||
|
||||
CBoolRef CScan::IsCriticalPropertyRef() const
|
||||
{
|
||||
const uint kIsCriticalIdMP1 = 0x4;
|
||||
const uint kIsCriticalIdMP2 = 0x7B714814;
|
||||
|
||||
IProperty* pProperty = mpTemplate->Properties()->ChildByID(
|
||||
Game() <= EGame::Prime ? kIsCriticalIdMP1 : kIsCriticalIdMP2
|
||||
);
|
||||
|
||||
return CBoolRef( (void*) mPropertyData.data(), pProperty );
|
||||
}
|
||||
|
||||
/** CResource interface */
|
||||
CDependencyTree* CScan::BuildDependencyTree() const
|
||||
{
|
||||
CDependencyTree* pTree = new CDependencyTree();
|
||||
pTree->ParseProperties(Entry(), ScanData().Property(), ScanData().DataPointer());
|
||||
return pTree;
|
||||
}
|
||||
34
src/Core/Resource/Scan/CScan.h
Normal file
34
src/Core/Resource/Scan/CScan.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef CSCAN_H
|
||||
#define CSCAN_H
|
||||
|
||||
#include <Common/Common.h>
|
||||
#include "Core/Resource/Animation/CAnimationParameters.h"
|
||||
#include "Core/Resource/Script/CGameTemplate.h"
|
||||
#include "Core/Resource/Script/NGameList.h"
|
||||
|
||||
/** Scannable object parameters from SCAN assets */
|
||||
class CScan : public CResource
|
||||
{
|
||||
DECLARE_RESOURCE_TYPE(Scan)
|
||||
friend class CScanLoader;
|
||||
friend class CScanCooker;
|
||||
|
||||
/** Script template specifying scan data layout */
|
||||
CScriptTemplate* mpTemplate;
|
||||
|
||||
/** Scan property data */
|
||||
std::vector<uint8> mPropertyData;
|
||||
|
||||
public:
|
||||
CScan(CResourceEntry* pEntry = 0);
|
||||
CStructRef ScanData() const;
|
||||
|
||||
/** Convenience property accessors */
|
||||
CAssetRef ScanStringPropertyRef() const;
|
||||
CBoolRef IsCriticalPropertyRef() const;
|
||||
|
||||
/** CResource interface */
|
||||
virtual CDependencyTree* BuildDependencyTree() const override;
|
||||
};
|
||||
|
||||
#endif // CSCAN_H
|
||||
15
src/Core/Resource/Scan/ELogbookCategory.h
Normal file
15
src/Core/Resource/Scan/ELogbookCategory.h
Normal file
@@ -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
|
||||
60
src/Core/Resource/Scan/SScanParametersMP1.h
Normal file
60
src/Core/Resource/Scan/SScanParametersMP1.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#ifndef SSCANPARAMETERSMP1_H
|
||||
#define SSCANPARAMETERSMP1_H
|
||||
|
||||
#include "ELogbookCategory.h"
|
||||
#include <Common/Common.h>
|
||||
|
||||
/** Struct mapping to SCAN property layout in MP1 */
|
||||
enum class EScanSpeed
|
||||
{
|
||||
Normal = 0,
|
||||
Slow = 1,
|
||||
};
|
||||
|
||||
enum class EScanImagePane
|
||||
{
|
||||
Pane0 = 0,
|
||||
Pane1 = 1,
|
||||
Pane2 = 2,
|
||||
Pane3 = 3,
|
||||
Pane01 = 4,
|
||||
Pane12 = 5,
|
||||
Pane23 = 6,
|
||||
Pane012 = 7,
|
||||
Pane123 = 8,
|
||||
Pane0123 = 9,
|
||||
Pane4 = 10,
|
||||
Pane5 = 11,
|
||||
Pane6 = 12,
|
||||
Pane7 = 13,
|
||||
Pane45 = 14,
|
||||
Pane56 = 15,
|
||||
Pane67 = 16,
|
||||
Pane456 = 17,
|
||||
Pane567 = 18,
|
||||
Pane4567 = 19,
|
||||
None = -1
|
||||
};
|
||||
|
||||
struct SScanImage
|
||||
{
|
||||
CAssetID Texture;
|
||||
float AppearPercentage;
|
||||
EScanImagePane Pane;
|
||||
int32 AnimationCellWidth;
|
||||
int32 AnimationCellHeight;
|
||||
float AnimationSwapInterval;
|
||||
float FadeDuration;
|
||||
};
|
||||
|
||||
struct SScanParametersMP1
|
||||
{
|
||||
CAssetID GuiFrame;
|
||||
CAssetID String;
|
||||
EScanSpeed Speed;
|
||||
ELogbookCategory LogbookCategory;
|
||||
bool IsCritical;
|
||||
SScanImage ScanImages[4];
|
||||
};
|
||||
|
||||
#endif // SSCANPARAMETERSMP1_H
|
||||
@@ -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; }
|
||||
|
||||
|
||||
396
src/Core/Resource/StringTable/CStringTable.cpp
Normal file
396
src/Core/Resource/StringTable/CStringTable.cpp
Normal file
@@ -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;
|
||||
}
|
||||
103
src/Core/Resource/StringTable/CStringTable.h
Normal file
103
src/Core/Resource/StringTable/CStringTable.h
Normal file
@@ -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
|
||||
29
src/Core/Resource/StringTable/ELanguage.h
Normal file
29
src/Core/Resource/StringTable/ELanguage.h
Normal file
@@ -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);
|
||||
|
||||
44
src/Core/Tweaks/CTweakCooker.cpp
Normal file
44
src/Core/Tweaks/CTweakCooker.cpp
Normal file
@@ -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;
|
||||
}
|
||||
18
src/Core/Tweaks/CTweakCooker.h
Normal file
18
src/Core/Tweaks/CTweakCooker.h
Normal file
@@ -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
|
||||
62
src/Core/Tweaks/CTweakData.h
Normal file
62
src/Core/Tweaks/CTweakData.h
Normal file
@@ -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
|
||||
130
src/Core/Tweaks/CTweakLoader.cpp
Normal file
130
src/Core/Tweaks/CTweakLoader.cpp
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
18
src/Core/Tweaks/CTweakLoader.h
Normal file
18
src/Core/Tweaks/CTweakLoader.h
Normal file
@@ -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
|
||||
90
src/Core/Tweaks/CTweakManager.cpp
Normal file
90
src/Core/Tweaks/CTweakManager.cpp
Normal file
@@ -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();
|
||||
}
|
||||
29
src/Core/Tweaks/CTweakManager.h
Normal file
29
src/Core/Tweaks/CTweakManager.h
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user